Spec-Zone.ru › Kotlin 1.4

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 в обработчике завершения.

END_OF_DOCUMENT_MARKER

Когда функция 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

Spec-Zone.ru

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