Spec-Zone.ru › Kotlin 1.7

Взаимодействие с 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 сопоставляются со Swift/Objective-C и наоборот.

"->" и "<-" указывают на то, что сопоставление происходит только в одном направлении.

Kotlin

Swift

Objective-C

Примечания

class

class

@interface

примечание

interface

protocol

@protocol

constructor/create

Initializer

Initializer

примечание

Property

Property

Property

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

Method

Method

Method

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

suspend->

completionHandler:/async

completionHandler:

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

@Throws fun

throws

error:(NSError**)error

примечание

Extension

Extension

Category member

примечание

companion member <-

Class method or property

Class method or property

null

nil

nil

Singleton

shared or companion property

shared or companion property

примечание

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

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

примечание

Unit return type

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

примечание

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.

Установщики

Записываемые свойства 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.

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

В 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 (suspending functions) (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

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

Для решения этой проблемы, при определении своих обобщённых классов, если обобщённый тип никогда не должен быть нулём, укажите ограничение на ненулевой тип:

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.

Обычные методы могут быть переопределены с использованием ключевого слова 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").kotlinOptions.freeCompilerArgs += "-Xexport-kdoc"
    }
}
kotlin {
    targets.withType(org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget) {
        compilations.get("main").kotlinOptions.freeCompilerArgs += "-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 классов

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

© 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

Spec-Zone.ru

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