Kotlin/Native взаимодействие со Swift/Objective-C
Этот документ описывает некоторые детали взаимодействия Kotlin/Native со Swift/Objective-C.
Использование
Kotlin/Native обеспечивает двустороннее взаимодействие с Objective-C. Фреймворки и библиотеки Objective-C могут быть использованы в Kotlin-коде, если они должным образом импортированы в сборку (системные фреймворки импортируются по умолчанию). См., например, "Использование cinterop" в документации плагина Gradle. Библиотека Swift может быть использована в Kotlin-коде, если её API экспортировано в Objective-C с помощью @objc. Чистые Swift-модули пока не поддерживаются.
Kotlin-модули могут быть использованы в Swift/Objective-C-коде, если они скомпилированы в фреймворк (см. раздел "Цели и типы выходных данных" в документации плагина Gradle). См. пример калькулятора для примера.
Сопоставления
Таблица ниже показывает, как концепции Kotlin сопоставляются со Swift/Objective-C и наоборот.
"->" и "<-" указывают, что сопоставление происходит только в одном направлении.
| Kotlin | Swift | Objective-C | Примечания |
|---|---|---|---|
class | class | @interface | примечание |
interface | protocol | @protocol | |
constructor/create
| Инициализатор | Инициализатор | примечание |
| Свойство | Свойство | Свойство | примечание примечание |
| Метод | Метод | Метод | примечание примечание |
suspend -> | completionHandler: | примечание | |
@Throws fun | throws | error:(NSError**)error | примечание |
| Расширение | Расширение | Член категории | примечание |
companion member <- | Метод или свойство класса | Метод или свойство класса | |
null | nil | nil | |
Singleton | Singleton() | [Singleton singleton] | примечание |
| Примитивный тип | Примитивный тип / NSNumber
| примечание | |
Unit тип возврата | 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 | примечание |
| Тип функции | Тип функции | Тип указателя на блок | примечание |
| Встроенные классы | Не поддерживается | Не поддерживается | примечание |
Перевод имен
Классы Objective-C импортируются в Kotlin под своими оригинальными именами. Протоколы импортируются как интерфейсы с суффиксом имени Protocol, например @protocol Foo -> interface FooProtocol. Эти классы и интерфейсы помещаются в пакет, указанный в настройках сборки (platform.* пакеты для предварительно настроенных системных фреймворков).
Имена Kotlin-классов и интерфейсов добавляют префикс при импорте в Objective-C. Префикс получен из имени фреймворка.
Инициализаторы
Инициализаторы 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 нет понятия проверочных исключений, все Kotlin-исключения не проверяемые. Swift имеет только проверочные ошибки. Поэтому, если код Swift или Objective-C вызывает Kotlin-метод, который вызывает исключение для обработки, то Kotlin-метод должен быть помечен аннотацией @Throws, указывающей список «ожидаемых» классов исключений.
При компиляции в фреймворк Objective-C/Swift функции, не имеющие или унаследовавшие аннотацию @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 как генерирующие исключения.
Расширения и члены категорий
Члены категорий 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 как класс с единственным экземпляром. Экземпляр доступен через метод-фабрику, т. е. как [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 object (см. синглетоны в таблице выше). Подводя итог:
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, укажите ограничение типа non-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. Исключением является то, что верхняя граница non-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.
Обычные методы могут быть переопределены с использованием ключевого слова 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
См. INTEROP.md для примера случая, когда библиотека использует некоторые функции C (например, небезопасные указатели, структуры и т. д.).
Не поддерживается
Некоторые функции языка программирования Kotlin пока не сопоставлены с соответствующими функциями Objective-C или Swift. В настоящее время следующие функции не должным образом представлены в сгенерированных заголовках фреймворков:
- встроенные классы (аргументы сопоставляются либо как базовый примитивный тип, либо как
id) - пользовательские классы, реализующие стандартные интерфейсы коллекций Kotlin (
List,Map,Set) и другие специальные классы - подклассы Kotlin классов Objective-C
© 2010–2020 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/reference/native/objc_interop.html