Взаимодействие с 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 |
Примечания |
|---|---|---|---|
|
|
|
|
|
|
|
|
|
Initializer |
Initializer |
|
Property |
Property |
Property |
|
Method |
Method |
Method |
|
|
|
|
|
|
|
|
|
Extension |
Extension |
Category member |
|
|
Class method or property |
Class method or property |
|
|
|
|
|
|
|
|
|
Primitive type |
Primitive type / |
||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 как вызывающие исключения.
Функции приостановки
Функции приостановки 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 для других типов обрабатываются как глобальные объявления с дополнительным параметром получателя. К этим типам относятся:
Тип
StringKotlinТипы коллекций Kotlin и их подтипы
Типы
interfaceKotlinПримитивные типы Kotlin
Классы
inlineKotlinТип
AnyKotlinТипы функций 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) или функций.
Возможность значения 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
© 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