Spec-Zone.ru › Kotlin 1.9

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

Использование этой аннотации требует opt-in.

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

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

Использование этой аннотации требует opt-in.

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

В 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, подвешенные функции Kotlin suspend также доступны для вызова из Swift как функции async без использования обработчиков завершения. В настоящее время эта функциональность является высокоэкспериментальной и имеет определенные ограничения. Подробности см. в этом вопросе YouTrack.

Дополнительные сведения о механизме async/await в Swift.

Расширения и члены категорий

Члены категорий Objective-C и расширений Swift обычно импортируются в Kotlin в качестве расширений. Поэтому эти объявления нельзя переопределять в Kotlin. А инициализаторы расширений недоступны в качестве конструкторов Kotlin.

В настоящее время есть два исключения. Начиная с Kotlin 1.8.20, члены категорий, объявленные в тех же заголовках, что и класс NSView (из фреймворка AppKit) или классы UIView (из фреймворка UIKit), импортируются как члены этих классов. Это означает, что вы можете переопределять методы, которые наследуются от NSView или UIView.

Расширения 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, укажите ограничение на ненулевой тип:

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 невозможно.

Обычные методы могут быть переопределены с помощью ключевого слова 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, такие как небезопасные указатели, структуры и т. д.

Экспорт комментариев 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 классы (аргументы отображаются как либо основной примитивный тип, либо id)

  • пользовательские классы, реализующие стандартные интерфейсы коллекций Kotlin (List, Map, Set) и другие специальные классы

  • Kotlin-подклассы Objective-C-классов

Последнее изменение: 11 мая 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