Spec-Zone.ru › Kotlin 1.8

Взаимодействие со 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

Примечания

class

class

@interface

примечание

interface

protocol

@protocol

constructor/create

Инициализатор

Инициализатор

примечание

Свойство

Свойство

Свойство

примечание 1, примечание 2

Метод

Метод

Метод

примечание 1, примечание 2

suspend->

completionHandler:/async

completionHandler:

примечание 1, примечание 2

@Throws fun

throws

error:(NSError**)error

примечание

Расширение

Расширение

Член категории

примечание

companion член <-

Метод или свойство класса

Метод или свойство класса

null

nil

nil

Singleton

shared или companion свойство

shared или companion свойство

примечание

Примитивный тип

Примитивный тип / 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. Префикс берется из имени фреймворка.

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 для методов или параметров.

Использование этой аннотации требует поддержки.

END_OF_DOCUMENT_MARKER

Ошибки и исключения

В 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 как выбрасывающие исключения.

Функции приостановки

Поддержка вызова suspend функций из кода Swift в виде async находится в стадии эксперимента. Она может быть удалена или изменена в любое время. Используйте ее только для оценки. Мы будем рады вашим отзывам об этом на YouTrack.

Функции приостановки 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

Доступ к объектам через [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 (см. синглетоны в таблице выше).

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

Возможность экспорта комментариев KDoc в сгенерированные заголовки Objective-C экспериментальная. Она может быть удалена или изменена в любое время. Требуется включение (см. подробности ниже), и вы должны использовать ее только в оценочных целях. Мы будем благодарны за ваши отзывы по этому вопросу на YouTrack.

По умолчанию, комментарии документации 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

Последнее изменение: 10 января 2023
Создание приложения с использованием C Interop и libcurl – учебник Kotlin/Native как фреймворк Apple – учебник

© 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

Spec-Zone.ru

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