Spec-Zone.ru › Kotlin 1.6

Взаимодействие с Swift/Objective-C

В данном документе рассматриваются некоторые детали взаимодействия Kotlin/Native с Swift/Objective-C.

Использование

Kotlin/Native обеспечивает двустороннее взаимодействие с Objective-C. Фреймворки и библиотеки Objective-C могут быть использованы в коде Kotlin, если они должным образом импортированы в сборку (системные фреймворки импортируются по умолчанию). Подробности см. в настройках компиляции. Библиотека Swift может быть использована в коде Kotlin, если ее API экспортирован в Objective-C с помощью @objc. Чистые модули Swift пока не поддерживаются.

Модули Kotlin могут быть использованы в коде Swift/Objective-C, если они скомпилированы в фреймворк (см. здесь, как объявить бинарные файлы). Пример см. в примере калькулятора.

Сопоставления

Таблица ниже показывает, как концепции Kotlin сопоставляются со Swift/Objective-C и наоборот.

"->" и "<-" указывают на то, что сопоставление осуществляется только в одну сторону.

Kotlin

Swift

Objective-C

Примечания

class

class

@interface

примечание

interface

protocol

@protocol

constructor/create

Initializer

Initializer

примечание

Property

Property

Property

примечание 1, примечание 2

Method

Method

Method

примечание 1, примечание 2

suspend->

completionHandler:/async

completionHandler:

примечание 1, примечание 2

@Throws fun

throws

error:(NSError**)error

примечание

Extension

Extension

Category member

примечание

companion member <-

Class method or property

Class method or property

null

nil

nil

Singleton

shared or companion property

shared or companion property

примечание

Primitive type

Primitive type / NSNumber

примечание

Unit return type

Void

void

String

String

NSString

String

NSMutableString

NSMutableString

примечание

List

Array

NSArray

MutableList

NSMutableArray

NSMutableArray

Set

Set

NSSet

MutableSet

NSMutableSet

NSMutableSet

примечание

Map

Dictionary

NSDictionary

MutableMap

NSMutableDictionary

NSMutableDictionary

примечание

Function type

Function type

Block pointer type

примечание

Inline classes

Unsupported

Unsupported

примечание

Перевод имен

Классы Objective-C импортируются в Kotlin с их исходными именами. Протоколы импортируются как интерфейсы с суффиксом имени Protocol, т.е. @protocol Foo-> interface FooProtocol. Эти классы и интерфейсы помещаются в пакет, указанный в конфигурации сборки (platform.* пакеты для предварительно настроенных системных фреймворков).

Имена классов и интерфейсов Kotlin добавляются в префикс при импорте в Objective-C. Префикс формируется из имени фреймворка.

Objective-C не поддерживает пакеты во фреймворке. Таким образом, компилятор Kotlin переименовывает классы Kotlin, которые имеют одинаковое имя, но разные пакеты в одном и том же фреймворке. Этот алгоритм пока не стабилен и может меняться между выпусками Kotlin. В качестве обходного пути можно переименовать конфликтующие классы Kotlin во фреймворке.

Инициализаторы

Инициализаторы Swift/Objective-C импортируются в Kotlin как конструкторы и фабричные методы с именем create. Последнее происходит с инициализаторами, объявленными в категории Objective-C или как расширение Swift, поскольку в Kotlin нет понятия конструкторов расширения.

Конструкторы Kotlin импортируются как инициализаторы в Swift/Objective-C.

Setters

Записываемые свойства Objective-C, переопределяющие свойства только для чтения суперкласса, представлены как метод setFoo() для свойства foo. То же самое относится к свойствам только для чтения протокола, которые реализованы как изменяемые.

Функции и свойства верхнего уровня

Функции и свойства верхнего уровня Kotlin доступны как члены специальных классов. Каждый файл Kotlin преобразуется в такой класс. Например,

// MyLibraryUtils.kt
package my.library

fun foo() {}

можно вызвать из Swift как

MyLibraryUtilsKt.foo()

Перевод имен методов

Как правило, метки аргументов Swift и части селектора Objective-C сопоставляются с именами параметров Kotlin. Однако эти две концепции имеют разную семантику, поэтому иногда методы Swift/Objective-C могут импортироваться с конфликтующей сигнатурой Kotlin. В этом случае к конфликтующим методам можно обратиться из Kotlin с помощью именованных аргументов, например:

[player moveTo:LEFT byMeters:17]
[player moveTo:UP byInches:42]

в Kotlin это будет:

player.moveTo(LEFT, byMeters = 17)
player.moveTo(UP, byInches = 42)

Ошибки и исключения

В Kotlin нет понятия проверенных исключений, все исключения Kotlin являются непроверенными. В Swift есть только проверяемые ошибки. Поэтому, если код Swift или Objective-C вызывает метод Kotlin, который вызывает исключение для обработки, то метод Kotlin должен быть помечен аннотацией @Throws, указывающей список "ожидаемых" классов исключений.

При компиляции во фреймворк Objective-C/Swift, функции, не являющиеся suspend, имеющие или наследующие аннотацию @Throws, представлены как методы, создающие NSError* в Objective-C и как методы throws в Swift. Представления для функций suspend всегда имеют параметр NSError*/Error в обработчике завершения.

Когда функция Kotlin, вызываемая из кода Swift/Objective-C, вызывает исключение, которое является экземпляром одного из классов, указанных в @Throws, или их подклассов, оно распространяется как NSError. Другие исключения Kotlin, достигающие Swift/Objective-C, считаются необработанными и приводят к завершению программы.

Функции suspend без @Throws распространяют только CancellationException как NSError. Функции, не являющиеся suspend, без @Throws вообще не распространяют исключения Kotlin.

Обратите внимание, что обратное преобразование пока не реализовано: методы Swift/Objective-C, вызывающие ошибки, не импортируются в Kotlin как вызывающие исключения.

Функции приостановки

Поддержка вызова функций suspend из кода Swift как async находится на стадии экспериментальной разработки. Она может быть удалена или изменена в любое время. Используйте её только для оценочных целей. Мы будем благодарны за ваши отзывы на YouTrack.

Функции приостановки Kotlin (suspend) представлены в сгенерированных заголовках Objective-C как функции с обратными вызовами или, говоря терминологией Swift/Objective-C, обработчиками завершения.

Начиная со Swift 5.5, функции suspend Kotlin также доступны для вызова из Swift как функции async без использования обработчиков завершения. В настоящее время эта функциональность находится на стадии активной разработки и имеет определённые ограничения. Подробнее см. этот запрос YouTrack.

Узнайте больше о механизме async/await в Swift.

Расширения и члены категорий

Члены категорий Objective-C и расширений Swift импортируются в Kotlin как расширения. Поэтому эти объявления нельзя переопределять в Kotlin. Также инициализаторы расширений недоступны в качестве конструкторов Kotlin.

Расширения Kotlin для «обычных» классов Kotlin импортируются в Swift и Objective-C как расширения и члены категорий соответственно. Расширения Kotlin для других типов обрабатываются как глобальные объявления с дополнительным параметром получателя. К этим типам относятся:

  • Тип String Kotlin

  • Типы коллекций Kotlin и их подтипы

  • Типы interface Kotlin

  • Примитивные типы Kotlin

  • Классы inline Kotlin

  • Тип Any Kotlin

  • Типы функций Kotlin и их подтипы

  • Классы и протоколы Objective-C

Синглетоны Kotlin

Синглетон Kotlin (созданный с помощью объявления object, включая companion object) импортируется в Swift/Objective-C как класс с одним экземпляром.

Экземпляр доступен через свойства shared и companion.

Для следующего кода Kotlin:

object MyObject {
    val x = "Some value"
}

class MyClass {
    companion object {
        val x = "Some value"
    }
}

Доступ к этим объектам осуществляется следующим образом:

MyObject.shared
MyObject.shared.x
MyClass.companion
MyClass.Companion.shared

Доступ к объектам через [MySingleton mySingleton] в Objective-C и MySingleton() в Swift устарел.

NSNumber

Обёртки примитивных типов Kotlin отображаются на специальные классы Swift/Objective-C. Например, обёртка типа kotlin.Int представлена как экземпляр класса KotlinInt в Swift (или ${prefix}Int в Objective-C, где prefix - префикс имён фреймворков). Эти классы являются производными от NSNumber, поэтому экземпляры являются полноценными NSNumber с поддержкой всех соответствующих операций.

Тип NSNumber не преобразуется автоматически в примитивные типы Kotlin при использовании в качестве типа параметра или возвращаемого значения Swift/Objective-C. Причина в том, что тип NSNumber не предоставляет достаточной информации о типе примитивного значения, которое он оборачивает, т.е. NSNumber статически неизвестен как, например, Byte, Boolean или Double. Поэтому значения примитивных типов Kotlin следует преобразовывать к/из NSNumber вручную (см. ниже).

NSMutableString

Класс Objective-C NSMutableString недоступен из Kotlin. Все экземпляры NSMutableString копируются при передаче в Kotlin.

Коллекции

Коллекции Kotlin преобразуются в коллекции Swift/Objective-C, как описано в таблице выше. Коллекции Swift/Objective-C преобразуются в Kotlin аналогичным образом, за исключением NSMutableSet и NSMutableDictionary. NSMutableSet не преобразуется в Kotlin MutableSet. Чтобы передать объект для Kotlin MutableSet, можно явно создать эту коллекцию Kotlin, например, создав её в Kotlin с использованием mutableSetOf(), или используя класс KotlinMutableSet в Swift (или ${prefix}MutableSet в Objective-C, где prefix - префикс имён фреймворков). То же самое касается и MutableMap.

Типы функций

Объекты типа функций Kotlin (например, лямбды) преобразуются в функции Swift/блоки Objective-C. Однако есть разница в том, как отображаются типы параметров и возвращаемых значений при преобразовании функции и типа функции. В последнем случае примитивные типы отображаются на их упакованные представления. Возвращаемое значение Kotlin Unit представляется как соответствующий синглетон Unit в Swift/Objective-C. Значение этого синглетона можно получить аналогично любому другому синглетону Kotlin (см. синглетоны в таблице выше). Подводя итог:

fun foo(block: (Int) -> Unit) { ... }

будет представлено в Swift как

func foo(block: (KotlinInt) -> KotlinUnit)

и может быть вызвано так

foo {
    bar($0 as! Int32)
    return KotlinUnit()
}

Обобщения

Objective-C поддерживает «лёгкие обобщения», определённые для классов, с относительно ограниченным набором функций. Swift может импортировать обобщения, определённые в классах, чтобы предоставить дополнительную информацию о типах компилятору.

Поддержка обобщений в Objective-C и Swift отличается от Kotlin, поэтому при переводе неизбежно потеряется некоторая информация, но поддерживаемые функции сохраняют значимую информацию.

Ограничения

Objective-C обобщения не поддерживают все возможности Kotlin или Swift, поэтому при переводе неизбежно потеряется некоторая информация.

Обобщения могут быть определены только для классов, а не для интерфейсов (протоколов в Objective-C и Swift) или функций.

Возможность значения null

Kotlin и Swift определяют возможность значения null в качестве части спецификации типа, в то время как Objective-C определяет возможность значения null для методов и свойств типа. Таким образом, следующее:

class Sample<T>() {
  fun myVal(): T
}

будет (логически) выглядеть так:

class Sample<T>() {
  fun myVal(): T?
}

Для поддержки потенциально nullable типа заголовок Objective-C должен определить myVal с nullable возвращаемым значением.

Для решения этой проблемы, при определении обобщённых классов, если обобщённый тип никогда не должен быть null, укажите ограничение на non-null тип:

class Sample<T : Any>() {
  fun myVal(): T
}

Это принудит заголовок Objective-C пометить myVal как non-null.

Изменчивость

Objective-C позволяет объявлять обобщения как ковариативные или контравариативные. Swift не поддерживает изменчивость. Классы обобщений, полученные из Objective-C, могут быть принудительно преобразованы по необходимости.

data class SomeData(val num: Int = 42) : BaseData()
class GenVarOut<out T : Any>(val arg: T)
let variOut = GenVarOut<SomeData>(arg: sd)
let variOutAny : GenVarOut<BaseData> = variOut as! GenVarOut<BaseData>

Ограничения

В Kotlin вы можете задавать верхние границы для обобщённого типа. Objective-C также поддерживает это, но эта поддержка недоступна в более сложных случаях и в настоящее время не поддерживается в межплатформенном взаимодействии Kotlin – Objective-C. Исключением является ситуация, когда верхняя граница non-null, тогда методы/свойства Objective-C станут non-null.

Отключение

Чтобы создать заголовок фреймворка без обобщений, добавьте флаг в конфигурацию компилятора:

binaries.framework {
     freeCompilerArgs += "-Xno-objc-generics"
}

Преобразование между сопоставленными типами

При написании кода Kotlin может потребоваться преобразовать объект из типа Kotlin в эквивалентный тип Swift/Objective-C (или наоборот). В этом случае можно использовать простое преобразование Kotlin, например:

val nsArray = listOf(1, 2, 3) as NSArray
val string = nsString as String
val nsNumber = 42 as NSNumber

Наследование

Наследование классов и интерфейсов Kotlin из Swift/Objective-C

Классы и интерфейсы Kotlin могут быть унаследованы классами и протоколами Swift/Objective-C.

Наследование классов и протоколов Swift/Objective-C из Kotlin

Классы и протоколы Swift/Objective-C могут быть унаследованы с помощью класса Kotlin final. Наследование не-final классов Kotlin, унаследованных от типов Swift/Objective-C, пока не поддерживается, поэтому создание сложной иерархии наследования от типов Swift/Objective-C невозможно.

Обычные методы могут быть переопределены с использованием ключевого слова override Kotlin. В этом случае переопределяемый метод должен иметь те же имена параметров, что и переопределяемый.

Иногда требуется переопределять инициализаторы, например, при наследовании UIViewController. Инициализаторы, импортированные как конструкторы Kotlin, могут быть переопределены конструкторами Kotlin, помеченными аннотацией @OverrideInit:

class ViewController : UIViewController {
    @OverrideInit constructor(coder: NSCoder) : super(coder)

    ...
}

Переопределяемый конструктор должен иметь те же имена и типы параметров, что и переопределяемый.

Для переопределения различных методов с конфликтующими подписями Kotlin можно добавить аннотацию @Suppress("CONFLICTING_OVERLOADS") к классу.

По умолчанию компилятор Kotlin/Native не разрешает вызывать неназначенный инициализатор Objective-C как конструктор super(...). Это поведение может быть неудобно, если назначенные инициализаторы не помечены должным образом в библиотеке Objective-C. Добавление disableDesignatedInitializerChecks = true в файл .def для этой библиотеки отключит эти проверки компилятора.

Особенности C

См. Взаимодействие с C для примера, где библиотека использует некоторые обычные функции C, такие как небезопасные указатели, структуры и т. д.

Неподдерживаемые функции

Некоторые функции языка программирования Kotlin еще не сопоставлены с соответствующими функциями Objective-C или Swift. В настоящее время следующие функции не должным образом экспонированы в сгенерированных заголовках фреймворка:

  • inline classes (аргументы отображаются либо как базовый примитивный тип, либо как id)

  • пользовательские классы, реализующие стандартные интерфейсы коллекций Kotlin (List, Map, Set) и другие специальные классы

  • Классы Kotlin, наследуемые от классов Objective-C

Последнее изменение: 07 апреля 2022
Создание приложения с использованием C Interop и libcurl – руководство Kotlin/Native как фреймворк Apple – руководство

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/native-objc-interop.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API