Взаимодействие со 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 Multiplatform Mobile Примере.
Скрытие Kotlin-деклараций
Если вы не хотите экспортировать Kotlin-декларации в Objective-C и Swift, используйте специальные аннотации:
@HiddenFromObjCскрывает Kotlin-декларацию от Objective-C и Swift. Аннотация отключает экспорт функции или свойства в Objective-C, делая ваш Kotlin-код более дружественным к Objective-C/Swift.-
@ShouldRefineInSwiftпомогает заменить Kotlin-декларацию на оберточную функцию, написанную на Swift. Аннотация помечает функцию или свойство какswift_privateв сгенерированном API Objective-C. Такие декларации получают префикс__, что делает их невидимыми для Swift.Вы по-прежнему можете использовать эти декларации в своём Swift-коде для создания дружественного к Swift API, но они не будут предлагаться в автозаполнении Xcode.
Для получения дополнительной информации о доработке деклараций Objective-C в Swift, обратитесь к официальной документации Apple.
Сопоставления
В таблице ниже показано, как концепции Kotlin сопоставляются со Swift/Objective-C и наоборот.
«->» и «<-» указывают, что сопоставление происходит только в одном направлении.
Kotlin |
Swift |
Objective-C |
Примечания |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
Инициализатор |
Инициализатор |
|
Свойство |
Свойство |
Свойство |
|
Метод |
Метод |
Метод |
|
|
|
|
|
|
|
|
|
Расширение |
Расширение |
Член категории |
|
|
Метод или свойство класса |
Метод или свойство класса |
|
|
|
|
|
|
|
|
|
Примитивный тип |
Примитивный тип / |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Тип функции |
Тип функции |
Тип указателя на блок |
|
Встроенные классы |
Не поддерживается |
Не поддерживается |
Перевод имен
Классы Objective-C импортируются в Kotlin со своими оригинальными именами. Протоколы импортируются как интерфейсы с добавленным суффиксом имени Protocol, например, @protocol Foo-> interface FooProtocol. Эти классы и интерфейсы помещаются в пакет, указанный в настройках сборки (platform.* пакеты для предварительно настроенных системных фреймворков).
Имена классов и интерфейсов Kotlin добавляют префикс при импорте в Objective-C. Префикс берется из имени фреймворка.
Objective-C не поддерживает пакеты в фреймворке. Поэтому компилятор Kotlin переименовывает классы Kotlin, имеющие одинаковое имя, но разные пакеты в одном фреймворке. Этот алгоритм пока не стабилен и может меняться между версиями Kotlin. В качестве обходного решения вы можете переименовать конфликтующие классы Kotlin в фреймворке.
Чтобы избежать переименования объявлений Kotlin, используйте аннотацию @ObjCName. Она указывает компилятору Kotlin использовать пользовательское имя Objective-C и Swift для классов, интерфейсов и других концепций Kotlin:
@ObjCName(swiftName = "MySwiftArray")
class MyKotlinArray {
@ObjCName("index")
fun indexOf(@ObjCName("of") element: String): Int = TODO()
}
// Usage with the ObjCName annotations
let array = MySwiftArray()
let index = array.index(of: "element")
Инициализаторы
Инициализаторы Swift/Objective-C импортируются в Kotlin как конструкторы и методы-фабрики с именем create. Последнее происходит с инициализаторами, объявленными в Objective-C категории или как Swift расширение, так как в Kotlin нет концепции расширенных конструкторов.
Конструкторы Kotlin импортируются как инициализаторы в Swift/Objective-C.
Свойства с установкой
Записываемые свойства 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.Any (equals(), hashCode() и toString()) сопоставляются с методами isEquals:, hash и description в Objective-C, и с методом isEquals(_:) и свойствами hash, description в Swift.
Можно указать более подходящее имя в Swift или Objective-C вместо переименования объявления Kotlin. Используйте аннотацию @ObjCName, которая указывает компилятору Kotlin использовать пользовательское имя Objective-C и Swift для методов или параметров.
Ошибки и исключения
В 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 как выбрасывающие исключения.
Функции приостановки
Функции приостановки 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 для других типов рассматриваются как объявления верхнего уровня с дополнительным параметром получателя. Эти типы включают:
Тип Kotlin
StringТипы коллекций Kotlin и подтипы
Типы Kotlin
interfaceПримитивные типы Kotlin
Классы Kotlin
inlineТип Kotlin
AnyТипы функций 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
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) или функций.
Необязательность
Kotlin и Swift оба определяют необязательность как часть спецификации типа, в то время как Objective-C определяет необязательность для методов и свойств типа. Таким образом, следующее:
class Sample<T>() {
fun myVal(): T
}
будет (логически) выглядеть так:
class Sample<T>() {
fun myVal(): T?
}
Для поддержки потенциально необязательного типа заголовок Objective-C должен определять myVal с необязательным возвращаемым значением.
Чтобы смягчить это, при определении своих обобщённых классов, если обобщённый тип никогда не должен быть null, укажите ограничение не-null типа:
class Sample<T : Any>() {
fun myVal(): T
}
Это заставит заголовок Objective-C пометить myVal как не-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. Исключением является то, что верхняя граница не-null сделает методы/свойства Objective-C не-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.
Обычные методы могут быть переопределены с помощью ключевого слова Kotlin override. В этом случае переопределяемый метод должен иметь те же имена параметров, что и переопределяемый.
Иногда требуется переопределять инициализаторы, например, при наследовании 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, такие как небезопасные указатели, структуры и т. д.
Экспорт комментариев KDoc в сгенерированные заголовки Objective-C
По умолчанию, комментарии документации KDocs не переводятся в соответствующие комментарии при генерации заголовка Objective-C.
Например, следующий Kotlin-код с KDoc:
/** * Prints the sum of the arguments. * Properly handles the case when the sum doesn't fit in 32-bit integer. */ fun printSum(a: Int, b: Int) = println(a.toLong() + b)
произведёт объявление Objective-C без каких-либо комментариев:
+ (void)printSumA:(int32_t)a b:(int32_t)b __attribute__((swift_name("printSum(a:b:)")));
Для включения экспорта комментариев KDoc добавьте следующую опцию компилятора в свой build.gradle(.kts):
kotlin {
targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
compilations.get("main").compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
}
}
kotlin {
targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
compilations.get("main").compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
}
}
После этого заголовок Objective-C будет содержать соответствующий комментарий:
/**
* Prints the sum of the arguments.
* Properly handles the case when the sum doesn't fit in 32-bit integer.
*/
+ (void)printSumA:(int32_t)a b:(int32_t)b __attribute__((swift_name("printSum(a:b:)")));
Известные ограничения:
Документация зависимостей не экспортируется, если она не скомпилирована с
-Xexport-kdoc. Данная функция экспериментальная, поэтому библиотеки, скомпилированные с этим флагом, могут быть несовместимы с другими версиями компилятора.Комментарии KDoc в основном экспортируются «как есть», многие функции KDoc (например,
@property) не поддерживаются.
Неподдерживается
Некоторые функции языка программирования Kotlin ещё не сопоставлены с соответствующими функциями Objective-C или Swift. В настоящее время следующие функции не правильно отображаются в сгенерированных заголовках фреймворка:
inline classes (аргументы сопоставляются как базовый примитивный тип или
id)пользовательские классы, реализующие стандартные интерфейсы коллекций Kotlin (
List,Map,Set) и другие специальные классыПодклассы Kotlin классов Objective-C
© 2010–2023 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