Взаимодействие с 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, подвешенные функции Kotlin suspend также доступны для вызова из 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, укажите ограничение на ненулевой тип:
class Sample<T : Any>() {
fun myVal(): T
}
Это заставит заголовок Objective-C пометить myVal как ненулевой.
Вариативность
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 ненулевыми.
Отключение
Чтобы создать заголовок фреймворка без обобщенных типов, добавьте флаг в конфигурацию компилятора:
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, такие как небезопасные указатели, структуры и т. д.
Экспорт комментариев 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 классы (аргументы отображаются как либо основной примитивный тип, либо
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