Spec-Zone.ru › Kotlin 1.7

Kotlin/Native в качестве фреймворка для Apple – учебник

Kotlin/Native обеспечивает двунаправленную совместимость с Objective-C/Swift. Фреймворки и библиотеки Objective-C могут использоваться в коде Kotlin. Модули Kotlin также могут использоваться в коде Swift/Objective-C. Помимо этого, Kotlin/Native имеет взаимодействие с C. Также есть учебник Kotlin/Native как динамическая библиотека для получения дополнительной информации.

В этом учебнике вы увидите, как использовать код Kotlin/Native из приложений Objective-C и Swift на macOS и iOS.

В этом учебнике вы:

  • создадите библиотеку Kotlin и скомпилируете её в фреймворк

  • рассмотрите сгенерированный код API Objective-C и Swift

  • используете фреймворк из Objective-C и Swift

  • Настройте Xcode для использования фреймворка для macOS и iOS

Создание библиотеки Kotlin

Компилятор Kotlin/Native может сгенерировать фреймворк для macOS и iOS из кода Kotlin. Созданный фреймворк содержит все объявления и двоичные файлы, необходимые для его использования с Objective-C и Swift. Лучший способ понять методы — попробовать их самим. Сначала создадим небольшую библиотеку Kotlin и используем её из программы Objective-C.

Создайте файл hello.kt с содержимым библиотеки:

package example

object Object {
  val field = "A"
}

interface Interface {
  fun iMember() {}
}

class Clazz : Interface {
  fun member(p: Int): ULong? = 42UL
}

fun forIntegers(b: Byte, s: UShort, i: Int, l: ULong?) { }
fun forFloats(f: Float, d: Double?) { }

fun strings(str: String?) : String {
  return "That is '$str' from C"
}

fun acceptFun(f: (String) -> String?) = f("Kotlin/Native rocks!")
fun supplyFun() : (String) -> String? = { "$it is cool!" }

Хотя можно использовать командную строку, как напрямую, так и объединяя её со скриптовым файлом (например, .sh или .bat файл), такой подход не масштабируется для больших проектов с сотнями файлов и библиотек. Поэтому лучше использовать компилятор Kotlin/Native с системой сборки, так как она помогает загружать и кэшировать двоичные файлы компилятора Kotlin/Native и библиотеки с транзитными зависимостями, а также запускать компилятор и тесты. Kotlin/Native может использовать систему сборки Gradle через плагин kotlin-multiplatform.

Мы рассмотрели основы настройки проекта, совместимого с IDE, с помощью Gradle в учебнике Базовое приложение Kotlin/Native. Пожалуйста, ознакомьтесь с ним, если вам нужны подробные первые шаги и инструкции по началу нового проекта Kotlin/Native и его открытию в IntelliJ IDEA. В этом учебнике мы рассмотрим расширенное использование C-совместимости Kotlin/Native и многоплатформенные сборки с Gradle.

Сначала создайте папку проекта. Все пути в этом учебнике будут относительными к этой папке. Иногда необходимо создать отсутствующие директории перед добавлением новых файлов.

Используйте следующий build.gradle(.kts) файл сборки Gradle:

plugins {
    kotlin("multiplatform") version "1.7.20"
}

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    binaries {
      framework {
        baseName = "Demo"
      }
    }
  }
}

tasks.wrapper {
  gradleVersion = "6.7.1"
  distributionType = Wrapper.DistributionType.ALL
}
plugins {
    id 'org.jetbrains.kotlin.multiplatform' version '1.7.20'
}

repositories {
    mavenCentral()
}

kotlin {
  macosX64("native") {
    binaries {
      framework {
        baseName = "Demo"
      }
    }
  }
}

wrapper {
  gradleVersion = "6.7.1"
  distributionType = "ALL"
}

Переместите файлы исходных кодов в папку src/nativeMain/kotlin в проекте. Это стандартный путь расположения исходных кодов при использовании плагина kotlin-multiplatform. Используйте следующий блок для настройки проекта для генерации динамической или общей библиотеки:

binaries {
  framework {
    baseName = "Demo"
  }  
}

Наряду с macOS X64, Kotlin/Native поддерживает macos arm64 и iOS arm32, arm64 и X64 целевые платформы. Вы можете заменить macosX64 соответствующими функциями, как показано в таблице:

Целевая платформа/устройство

Функция Gradle

macOS x86_64

macosX64()

macOS ARM 64

macosArm64()

iOS ARM 32

iosArm32()

iOS ARM 64

iosArm64()

iOS-симулятор (x86_64)

iosX64()

Запустите задачу linkNative Gradle для сборки библиотеки в IDE или вызовите следующую команду в консоли:

./gradlew linkNative

В зависимости от варианта, сборка генерирует фреймворк в папки build/bin/native/debugFramework и build/bin/native/releaseFramework. Давайте посмотрим, что внутри.

END_OF_DOCUMENT_MARKER

Заголовки сгенерированного фреймворка

Каждый из созданных фреймворков содержит заголовочный файл в <Framework>/Headers/Demo.h. Заголовки не зависят от целевой платформы (по крайней мере, в Kotlin/Native v.0.9.2). Он содержит определения для нашего кода Kotlin и несколько объявлений в масштабе Kotlin.

Способ экспорта символов Kotlin/Native может измениться без предварительного уведомления.

Объявления среды выполнения Kotlin/Native

Всмотритесь в объявления среды выполнения Kotlin:

NS_ASSUME_NONNULL_BEGIN

@interface KotlinBase : NSObject
- (instancetype)init __attribute__((unavailable));
+ (instancetype)new __attribute__((unavailable));
+ (void)initialize __attribute__((objc_requires_super));
@end;

@interface KotlinBase (KotlinBaseCopying) <NSCopying>
@end;

__attribute__((objc_runtime_name("KotlinMutableSet")))
__attribute__((swift_name("KotlinMutableSet")))
@interface DemoMutableSet<ObjectType> : NSMutableSet<ObjectType>
@end;

__attribute__((objc_runtime_name("KotlinMutableDictionary")))
__attribute__((swift_name("KotlinMutableDictionary")))
@interface DemoMutableDictionary<KeyType, ObjectType> : NSMutableDictionary<KeyType, ObjectType>
@end;

@interface NSError (NSErrorKotlinException)
@property (readonly) id _Nullable kotlinException;
@end;

Классы Kotlin имеют базовый класс KotlinBase в Objective-C, класс расширяет класс NSObject там. Также существуют обёртки для коллекций и исключений. Большинство типов коллекций сопоставляются с аналогичными типами коллекций с другой стороны:

Kotlin

Swift

Objective-C

List

Array

NSArray

MutableList

NSMutableArray

NSMutableArray

Set

Set

NSSet

Map

Dictionary

NSDictionary

MutableMap

NSMutableDictionary

NSMutableDictionary

Числа Kotlin и NSNumber

Следующая часть <Framework>/Headers/Demo.h содержит сопоставления числовых типов между Kotlin/Native и NSNumber. Существует базовый класс, называемый DemoNumber в Objective-C и KotlinNumber в Swift. Он расширяет NSNumber. Также есть дочерние классы для каждого числового типа Kotlin:

Kotlin

Swift

Objective-C

Простой тип

-

KotlinNumber

<Package>Number

-

Byte

KotlinByte

<Package>Byte

char

UByte

KotlinUByte

<Package>UByte

unsigned char

Short

KotlinShort

<Package>Short

short

UShort

KotlinUShort

<Package>UShort

unsigned short

Int

KotlinInt

<Package>Int

int

UInt

KotlinUInt

<Package>UInt

unsigned int

Long

KotlinLong

<Package>Long

long long

ULong

KotlinULong

<Package>ULong

unsigned long long

Float

KotlinFloat

<Package>Float

float

Double

KotlinDouble

<Package>Double

double

Boolean

KotlinBoolean

<Package>Boolean

BOOL/Bool

Каждый числовой тип имеет метод класса для создания нового экземпляра из соответствующего простого типа. Также существует метод экземпляра для извлечения простого значения обратно. Схематически объявления выглядят так:

__attribute__((objc_runtime_name("Kotlin__TYPE__")))
__attribute__((swift_name("Kotlin__TYPE__")))
@interface Demo__TYPE__ : DemoNumber
- (instancetype)initWith__TYPE__:(__CTYPE__)value;
+ (instancetype)numberWith__TYPE__:(__CTYPE__)value;
@end;

Где __TYPE__ — одно из имён простых типов, а __CTYPE__ — соответствующий тип Objective-C, например, initWithChar(char).

Эти типы используются для сопоставления упакованных числовых типов Kotlin в Objective-C и Swift. В Swift вы можете просто вызвать конструктор для создания экземпляра, например, KotlinLong(value: 42).

Классы и объекты из Kotlin

Давайте посмотрим, как class и object сопоставляются с Objective-C и Swift. Сгенерированный файл <Framework>/Headers/Demo.h содержит точные определения для Class, Interface и Object:

NS_ASSUME_NONNULL_BEGIN

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Object")))
@interface DemoObject : KotlinBase
+ (instancetype)alloc __attribute__((unavailable));
+ (instancetype)allocWithZone:(struct _NSZone *)zone __attribute__((unavailable));
+ (instancetype)object __attribute__((swift_name("init()")));
@property (readonly) NSString *field;
@end;

__attribute__((swift_name("Interface")))
@protocol DemoInterface
@required
- (void)iMember __attribute__((swift_name("iMember()")));
@end;

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("Clazz")))
@interface DemoClazz : KotlinBase <DemoInterface>
- (instancetype)init __attribute__((swift_name("init()"))) __attribute__((objc_designated_initializer));
+ (instancetype)new __attribute__((availability(swift, unavailable, message="use object initializers instead")));
- (DemoLong * _Nullable)memberP:(int32_t)p __attribute__((swift_name("member(p:)")));
@end;

Код полон атрибутов Objective-C, которые предназначены для помощи в использовании фреймворка как из Objective-C, так и из Swift. DemoClazz, DemoInterface и DemoObject создаются для Clazz, Interface и Object соответственно. Interface преобразуется в @protocol, как class, так и object представлены как @interface. Префикс Demo происходит от параметра -output компилятора kotlinc-native и имени фреймворка. Вы видите здесь, что возвращаемый тип с возможностью значений null ULong? преобразуется в DemoLong* в Objective-C.

Глобальные объявления из Kotlin

Все глобальные функции из Kotlin преобразуются в DemoLibKt в Objective-C и в LibKt в Swift, где Demo — имя фреймворка, устанавливаемое параметром -output kotlinc-native.

NS_ASSUME_NONNULL_BEGIN

__attribute__((objc_subclassing_restricted))
__attribute__((swift_name("LibKt")))
@interface DemoLibKt : KotlinBase
+ (void)forIntegersB:(int8_t)b s:(int16_t)s i:(int32_t)i l:(DemoLong * _Nullable)l __attribute__((swift_name("forIntegers(b:s:i:l:)")));
+ (void)forFloatsF:(float)f d:(DemoDouble * _Nullable)d __attribute__((swift_name("forFloats(f:d:)")));
+ (NSString *)stringsStr:(NSString * _Nullable)str __attribute__((swift_name("strings(str:)")));
+ (NSString * _Nullable)acceptFunF:(NSString * _Nullable (^)(NSString *))f __attribute__((swift_name("acceptFun(f:)")));
+ (NSString * _Nullable (^)(NSString *))supplyFun __attribute__((swift_name("supplyFun()")));
@end;

Вы видите, что String Kotlin и NSString* Objective-C сопоставляются прозрачно. Аналогично, тип Unit из Kotlin сопоставляется с void. Мы видим, что примитивные типы сопоставляются напрямую. Примитивные типы без возможности значений null сопоставляются прозрачно. Примитивные типы с возможностью значений null сопоставляются с типами Kotlin<TYPE>*, как показано в таблице выше. Обе функции высшего порядка acceptFunF и supplyFun включены и принимают блоки Objective-C.

Дополнительную информацию о всех других подробностях сопоставления типов можно найти в статье документации Objective-C Interop

Сборщик мусора и подсчёт ссылок

Objective-C и Swift используют подсчёт ссылок. Kotlin/Native также имеет собственный сборщик мусора. Сборщик мусора Kotlin/Native интегрирован с подсчётом ссылок Objective-C/Swift. Вам не нужно использовать ничего особенного для управления временем существования экземпляров Kotlin/Native из Swift или Objective-C.

Использование кода из Objective-C

Давайте вызовем фреймворк из Objective-C. Для этого создайте файл main.m со следующим содержимым:

#import <Foundation/Foundation.h>
#import <Demo/Demo.h>

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [[DemoObject object] field];
        
        DemoClazz* clazz = [[ DemoClazz alloc] init];
        [clazz memberP:42];
        
        [DemoLibKt forIntegersB:1 s:1 i:3 l:[DemoULong numberWithUnsignedLongLong:4]];
        [DemoLibKt forIntegersB:1 s:1 i:3 l:nil];
        
        [DemoLibKt forFloatsF:2.71 d:[DemoDouble numberWithDouble:2.71]];
        [DemoLibKt forFloatsF:2.71 d:nil];
        
        NSString* ret = [DemoLibKt acceptFunF:^NSString * _Nullable(NSString * it) {
            return [it stringByAppendingString:@" Kotlin is fun"];
        }];
        
        NSLog(@"%@", ret);
        return 0;
    }
}

Здесь вы вызываете классы Kotlin непосредственно из кода Objective-C. Kotlin object имеет функцию метода класса object, которая позволяет нам получить единственный экземпляр объекта и вызывать методы Object на нём. Широко распространённый шаблон используется для создания экземпляра класса Clazz. Вы вызываете [[ DemoClazz alloc] init] на Objective-C. Вы также можете использовать [DemoClazz new] для конструкторов без параметров. Глобальные объявления из исходников Kotlin находятся в области видимости класса DemoLibKt в Objective-C. Все методы преобразуются в методы класса этого класса. Функция strings преобразуется в функцию DemoLibKt.stringsStr в Objective-C, вы можете передать NSString непосредственно ей. Результат также виден как NSString.

Использование кода из Swift

Фреймворк, скомпилированный с помощью Kotlin/Native, содержит вспомогательные атрибуты, которые упрощают его использование в Swift. Преобразуйте предыдущий пример на Objective-C в Swift. В результате вы получите следующий код в main.swift:

import Foundation
import Demo

let kotlinObject = Object()
assert(kotlinObject === Object(), "Kotlin object has only one instance")

let field = Object().field

let clazz = Clazz()
clazz.member(p: 42)

LibKt.forIntegers(b: 1, s: 2, i: 3, l: 4)
LibKt.forFloats(f: 2.71, d: nil)

let ret = LibKt.acceptFun { "\($0) Kotlin is fun" }
if (ret != nil) {
  print(ret!)
}

Код на Kotlin преобразуется в очень похожий код на Swift. Однако есть некоторые небольшие различия. В Kotlin любой object имеет только один экземпляр. У Kotlin object Object теперь есть конструктор в Swift, и мы используем синтаксис Object() для доступа к единственному экземпляру. Экземпляр всегда остается одним и тем же в Swift, поэтому Object() === Object() является истинным. Имена методов и свойств переводятся без изменений. Kotlin String также преобразуется в Swift String. Swift также скрывает от нас NSNumber* упаковку. Мы также можем передать Swift замыкание в Kotlin и вызвать Kotlin лямбда-функцию из Swift.

Дополнительную информацию о сопоставлении типов можно найти в статье Объектно-ориентированный интерфейс с Objective-C.

Xcode и зависимости фреймворка

Необходимо настроить проект Xcode для использования нашего фреймворка. Настройка зависит от целевой платформы.

Xcode для macOS-цели

Во-первых, в вкладке Общие конфигурации цели, в разделе Связанные фреймворки и библиотеки, необходимо включить наш фреймворк. Это заставит Xcode просмотреть наш фреймворк и разрешить импорты как из Objective-C, так и из Swift.

На втором шаге необходимо настроить путь поиска фреймворка в сгенерированном двоичном файле. Он также известен как rpath или путь поиска во время выполнения. Двоичный файл использует путь для поиска необходимых фреймворков. Не рекомендуется устанавливать дополнительные фреймворки в ОС, если это не требуется. Вы должны понимать структуру вашего будущего приложения, например, у вас может быть папка Frameworks в пакете приложения со всеми используемыми фреймворками. Параметр @rpath можно настроить в Xcode. Необходимо открыть конфигурацию проекта и найти раздел Пути поиска runpath. Здесь вы указываете относительный путь к скомпилированному фреймворку.

Xcode для iOS-целей

Во-первых, необходимо включить скомпилированный фреймворк в проект Xcode. Для этого добавьте фреймворк в раздел Фреймворки, библиотеки и встроенное содержимое вкладки Общие страницы конфигурации цели.

На втором шаге необходимо включить путь фреймворка в раздел Пути поиска фреймворков вкладки Настройки сборки конфигурации цели. Для упрощения настройки можно использовать макрос $(PROJECT_DIR).

Симулятор iOS требует фреймворка, скомпилированного для ios_x64 цели, папки iOS_sim в нашем случае.

Эта тема на Stackoverflow содержит несколько дополнительных рекомендаций. Кроме того, менеджер пакетов CocoaPods может быть полезен для автоматизации процесса.

Следующие шаги

Kotlin/Native имеет двусторонний интерфейс с языками Objective-C и Swift. Kotlin объекты интегрируются с подсчетом ссылок Objective-C/Swift. Неиспользуемые Kotlin объекты автоматически удаляются. Дополнительную информацию об implementation details интерфейса можно найти в статье Объектно-ориентированный интерфейс с Objective-C. Конечно, можно импортировать существующий фреймворк и использовать его из Kotlin. Kotlin/Native поставляется с хорошим набором предварительно импортированных системных фреймворков.

Kotlin/Native также поддерживает интерфейс C. Ознакомьтесь с руководством Kotlin/Native в качестве динамической библиотеки для этого.

Последнее изменение: 12 августа 2022 г.
Взаимодействие с Swift/Objective-C Обзор и настройка CocoaPods

© 2010–2022 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/apple-framework.html

Spec-Zone.ru

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