Сборка iOS-фреймворка и подключение к Xcode

Как общий Kotlin-модуль попадает в Xcode и становится обычной зависимостью Swift-проекта.

XCFramework — упаковка скомпилированного Kotlin/Native-кода в формат, который Xcode подключает как библиотеку, с заголовками Objective-C для вызова из Swift.

Что именно собирается для iOS

Для iOS Kotlin/Native генерирует .framework — бинарник плюс сгенерированные Objective-C заголовки. Из этих заголовков Swift «видит» ваши классы и функции. Чтобы упаковать сразу несколько архитектур (устройство + симулятор) в один артефакт, используют XCFramework.

kotlin {
    val xcf = XCFramework()
    listOf(iosX64(), iosArm64(), iosSimulatorArm64()).forEach {
        it.binaries.framework {
            baseName = "Shared"
            xcf.add(this)
        }
    }
}

Два способа подключения

Первый — прямая интеграция: Xcode при сборке вызывает Gradle-задачу, которая пересобирает фреймворк. Удобно, пока общий код и iOS-приложение в одном репозитории. Второй — публикация XCFramework (например, через CocoaPods или Swift Package Manager) и подключение как внешней зависимости. Это лучше, когда iOS-команда работает отдельно и не хочет держать у себя Kotlin-тулчейн.

Прямая интеграция через скрипт сборки

В настройках Xcode-таргета добавляют Run Script Phase, который дёргает Gradle:

cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

Эта задача собирает фреймворк под текущую конфигурацию (Debug/Release, устройство/симулятор) и кладёт туда, где Xcode его подхватит.

Как это выглядит в Swift

После подключения общий модуль импортируется как обычный фреймворк:

import Shared

let repo = OrderRepository()
repo.loadOrders { orders, error in
    // orders — это ваши Kotlin-модели, видимые из Swift
}

Как работает под капотом

Компилятор Kotlin/Native экспортирует публичный API общего модуля в виде Objective-C интерфейса (заголовочный файл .h внутри фреймворка). Swift поверх Objective-C работает прозрачно, поэтому Kotlin-классы выглядят почти как Swift-типы. «Почти» — потому что трансляция не идеальна: generic'и, корутины и enum'ы маппятся с оговорками, которые мы разберём в разделе про интероп.

Частые ошибки

Типичная проблема — собрали фреймворк под устройство, а запускают на симуляторе (или наоборот), и Xcode ругается на несовместимую архитектуру. XCFramework решает это, упаковывая все варианты. Вторая ошибка — менять Kotlin-код и не пересобирать фреймворк; при прямой интеграции скрипт делает это сам, но при ручном подключении легко получить «старую» версию API.

Итоги

  • iOS получает общий код в виде .framework/XCFramework с Obj-C заголовками.
  • Подключать можно прямой интеграцией (Gradle из Xcode) или как опубликованную зависимость.
  • В Swift общий модуль импортируется обычным import.
  • XCFramework избавляет от путаницы «устройство против симулятора».
Проверьте себя
1. Что Kotlin/Native генерирует, чтобы Swift мог вызывать общий код?
AJSON-схему API
BObjective-C заголовки внутри .framework
CPython-биндинги
DJavaScript-обёртку
2. Зачем нужен XCFramework вместо одиночного .framework?
AОн меньше весит
BОн упаковывает несколько архитектур (устройство и симулятор) в один артефакт
CОн работает быстрее
DОн не требует Xcode