Импорт библиотек C, Objective-C и Swift
Kotlin/Native позволяет импортировать библиотеки C и Objective-C. Кроме того, в проектах Kotlin/Native можно обойти ограничения на импорт чистых библиотек Swift.
Стабильность импорта библиотек C и Objective-C
Поддержка импорта библиотек C и Objective-C в настоящее время находится на стадии Beta.
Одна из основных причин статуса Beta заключается в том, что использование библиотек C и Objective-C может повлиять на совместимость вашего кода с разными версиями Kotlin, зависимостей и Xcode. В этом руководстве перечислены проблемы совместимости, часто возникающие на практике, проблемы, возникающие лишь в некоторых случаях, а также гипотетические потенциальные проблемы.
Для простоты мы разделим библиотеки C и Objective-C, или нативные библиотеки, на следующие категории:
Платформенные библиотеки, которые Kotlin предоставляет по умолчанию для доступа к «системным» нативным библиотекам на каждой платформе.
Сторонние библиотеки — все остальные нативные библиотеки, для использования которых с Kotlin требуется дополнительная настройка.
Для этих двух видов нативных библиотек действуют разные особенности совместимости.
Платформенные библиотеки
Платформенные библиотеки поставляются вместе с компилятором Kotlin/Native. Поэтому при использовании в проекте разных версий Kotlin вы получаете разные версии платформенных библиотек. Для целевых платформ Apple (например, iOS) платформенные библиотеки создаются на основе версии Xcode, поддерживаемой конкретной версией компилятора.
API нативных библиотек, поставляемых с SDK Xcode, меняются с каждой версией Xcode. Даже если такие изменения совместимы на уровне исходного кода и двоичной совместимости в нативном языке, из-за реализации взаимодействия они могут стать критическими для Kotlin.
В результате обновление версии Kotlin в проекте может привести к критическому изменению в платформенных библиотеках. Это может иметь значение в двух случаях:
В платформенных библиотеках произошло несовместимое изменение исходного кода, которое влияет на компиляцию исходного кода вашего проекта. Обычно его легко исправить.
-
В платформенных библиотеках произошло двоичное несовместимое изменение, которое влияет на некоторые ваши зависимости. Как правило, простого обходного решения нет: потребуется дождаться, пока разработчик библиотеки исправит проблему со своей стороны, например обновив версию Kotlin.
Обновляя версию Xcode, используемую для создания платформенных библиотек, команда JetBrains прилагает разумные усилия, чтобы избежать критических изменений в них. Если такое изменение возможно, команда проводит анализ его влияния и либо решает не учитывать конкретное изменение (поскольку затронутый API используется редко), либо применяет специальное исправление.
Еще одной потенциальной причиной критических изменений в платформенных библиотеках могут стать изменения алгоритма преобразования нативных API в Kotlin. Команда JetBrains также прилагает разумные усилия, чтобы избежать критических изменений в таких случаях.
Использование новых классов Objective-C из платформенных библиотек
Компилятор Kotlin не запрещает использовать классы Objective-C, недоступные для целевой версии развертывания.
Например, если целевая версия развертывания — iOS 17.0, а вы используете класс, появившийся только в iOS 18.0, компилятор не выдаст предупреждение, и приложение может аварийно завершиться при запуске на устройстве с iOS 17.0. Более того, сбой произойдет, даже если выполнение никогда не дойдет до этих участков кода, поэтому проверок версии недостаточно.
Подробнее см. в разделе Статическая привязка.
Сторонние библиотеки
Помимо системных платформенных библиотек, Kotlin/Native позволяет импортировать сторонние нативные библиотеки. Например, можно использовать интеграцию с CocoaPods или настроить конфигурацию cinterops.
Импорт библиотек при несовпадении версий Xcode
Импорт сторонних нативных библиотек может привести к проблемам совместимости с разными версиями Xcode.
При обработке нативной библиотеки компилятор обычно использует заголовочные файлы из локально установленной версии Xcode, поскольку почти все заголовки нативных библиотек импортируют «стандартные» заголовки (например, stdint.h), поставляемые вместе с Xcode.
Именно поэтому версия Xcode влияет на импорт нативных библиотек в Kotlin. Это также одна из причин, по которым кросс-компиляция для целевых платформ Apple на компьютере без Mac по-прежнему невозможна при использовании сторонних нативных библиотек.
Каждая версия Kotlin наиболее совместима с одной конкретной версией Xcode. Это рекомендуемая версия, на которой соответствующая версия Kotlin тестируется чаще всего. Проверьте совместимость с конкретной версией Xcode в таблице совместимости.
Использовать более новые или старые версии Xcode часто возможно, но это может привести к проблемам, обычно связанным с импортом сторонних нативных библиотек.
Версия Xcode новее рекомендуемой
Использование версии Xcode новее рекомендуемой может нарушить работу некоторых функций Kotlin. Больше всего от этого страдает импорт сторонних нативных библиотек. Часто он вообще не работает с неподдерживаемой версией Xcode.
Версия Xcode старше рекомендуемой
Как правило, Kotlin хорошо работает со старыми версиями Xcode. Иногда могут возникать проблемы, которые чаще всего приводят к следующему:
API Kotlin ссылается на несуществующий тип, как в случае KT-71694.
Тип из системной библиотеки включается в API Kotlin нативной библиотеки. В этом случае проект успешно компилируется, но системный нативный тип добавляется в пакет вашей нативной библиотеки. Например, этот тип может неожиданно появиться в автодополнении IDE.
Если ваша библиотека Kotlin успешно компилируется со старой версией Xcode, ее можно безопасно публиковать, если только вы не используете типы из сторонней библиотеки в API своей библиотеки Kotlin.
Использование транзитивной зависимости от сторонней нативной библиотеки
Если библиотека Kotlin в вашем проекте импортирует стороннюю нативную библиотеку как часть своей реализации, ваш проект также получает доступ к этой нативной библиотеке. Это происходит потому, что Kotlin/Native не различает типы зависимостей api и implementation, поэтому нативные библиотеки всегда становятся зависимостями типа api.
Использование такой транзитивной нативной зависимости сопряжено с дополнительными проблемами совместимости. Например, изменение, внесенное разработчиком библиотеки Kotlin, может сделать представление нативной библиотеки в Kotlin несовместимым, что приведет к проблемам совместимости при обновлении библиотеки Kotlin.
Поэтому вместо использования транзитивной зависимости настройте взаимодействие с той же нативной библиотекой напрямую. Для этого укажите для нативной библиотеки другое имя пакета, как при использовании пользовательского имени пакета, чтобы избежать проблем совместимости.
Использование нативных типов в API библиотеки
Если вы публикуете библиотеку Kotlin, будьте осторожны при использовании нативных типов в ее API. В будущем такие варианты использования могут быть изменены, чтобы исправить проблемы совместимости и другие проблемы, что повлияет на пользователей вашей библиотеки.
В некоторых случаях использование нативных типов в API библиотеки необходимо для ее предназначения, например, когда библиотека Kotlin по сути предоставляет расширения для нативной библиотеки. Если это не ваш случай, избегайте использования нативных типов в API библиотеки или сведите его к минимуму.
Эта рекомендация относится только к использованию нативных типов в API библиотек и не касается кода приложения. Она также не распространяется на реализации библиотек, например:
// Be extra careful! Native types are used in the library API: public fun createUIView(): UIView public fun handleThirdPartyNativeType(c: ThirdPartyNativeType) // Be careful as usual; native types are not used in the library API: internal fun createUIViewController(): UIViewController public fun getDate(): String = NSDate().toString()
Публикация библиотеки, использующей стороннюю библиотеку
Если вы публикуете библиотеку Kotlin, использующую сторонние нативные библиотеки, можно предпринять несколько шагов, чтобы избежать проблем совместимости.
Используйте пользовательское имя пакета
Использование пользовательских имен пакетов для сторонних нативных библиотек может помочь избежать проблем совместимости.
При импорте нативной библиотеки в Kotlin ей присваивается имя пакета Kotlin. Если оно не уникально, у пользователей библиотеки может возникнуть конфликт. Например, если нативная библиотека импортирована под тем же именем пакета в другой части проекта пользователя или в других зависимостях, эти два варианта использования будут конфликтовать.
В таком случае компиляция может завершиться ошибкой Linking globals named '...': symbol multiply defined!. Однако возможны и другие ошибки или даже успешная компиляция.
Чтобы использовать пользовательское имя для сторонних нативных библиотек:
При импорте нативной библиотеки через интеграцию с CocoaPods используйте свойство
packageNameв блокеpod {}сценария сборки Gradle.При импорте нативной библиотеки с конфигурацией
cinteropsиспользуйте свойствоpackageNameв блоке конфигурации.
Проверяйте совместимость со старыми версиями Kotlin
При публикации библиотеки Kotlin использование сторонней нативной библиотеки может повлиять на ее совместимость с другими версиями Kotlin, в частности:
-
Для библиотек Kotlin Multiplatform не гарантируется прямая совместимость (возможность использовать библиотеку, скомпилированную более новым компилятором, в старом компиляторе).
На практике это работает в некоторых случаях, однако использование нативных библиотек может дополнительно ограничить прямую совместимость.
-
Библиотеки Kotlin Multiplatform обеспечивают обратную совместимость (возможность использовать библиотеки, созданные более старой версией, в новом компиляторе).
Использование нативной библиотеки в библиотеке Kotlin обычно не должно влиять на ее обратную совместимость. Однако это повышает вероятность ошибок компилятора, влияющих на совместимость.
Не встраивайте статические библиотеки
При импорте нативной библиотеки можно включить связанную с ней статическую библиотеку (файл .a) с помощью параметра компилятора -staticLibrary или свойства staticLibraries в файле .def. В этом случае пользователям вашей библиотеки не придется работать с нативными зависимостями и параметрами компоновщика.
Однако невозможно каким-либо образом настроить использование включенной статической библиотеки: ее нельзя ни исключить, ни заменить. Поэтому пользователи не смогут устранить потенциальные конфликты с другими библиотеками Kotlin, включающими ту же статическую библиотеку, или изменить ее версию.
Развитие поддержки нативных библиотек
В настоящее время использование C и Objective-C в проектах Kotlin может приводить к проблемам совместимости; некоторые из них перечислены в этом руководстве. Для их устранения в будущем могут потребоваться критические изменения, что само по себе усугубляет проблему совместимости.
Импорт библиотек Swift
Kotlin/Native не поддерживает прямой импорт чистых библиотек Swift. Однако есть несколько способов обойти это ограничение.
Один из способов — использовать ручной мост Objective-C. Для этого необходимо написать собственные оболочки Objective-C и файлы .def, а затем использовать эти оболочки через cinterop.
Однако в большинстве случаев мы рекомендуем использовать подход обратного импорта: определить ожидаемое поведение на стороне Kotlin, реализовать фактическую функциональность на стороне Swift и передать ее обратно в Kotlin.
Ожидаемую часть можно определить одним из следующих способов:
Создать интерфейс. Подход с интерфейсом лучше масштабируется при наличии множества функций и упрощает тестирование.
Использовать замыкания Swift. Они отлично подходят для быстрого создания прототипов, но у этого подхода есть ограничения — например, он не позволяет хранить состояние.
Использовать экспорт Swift. Можно реализовать интерфейс Kotlin непосредственно в Swift и передать объект Swift обратно в Kotlin без моста Objective-C.
Рассмотрим пример обратного импорта библиотеки Swift CryptoKit в проект Kotlin:
-
На стороне Kotlin создайте интерфейс, описывающий ожидания Kotlin от Swift:
// CryptoProvider.kt interface CryptoProvider { fun hashMD5(input: String): String } -
На стороне Kotlin передайте платформенную реализацию из
MainViewControllerв компонуемый элементAppв качестве параметра и используйте ее там, где это необходимо:// App.kt @Composable fun App(cryptoProvider: CryptoProvider) { // Example usage inside your UI val hashed = cryptoProvider.hashMD5("Hello, world!") androidx.compose.material3.Text("Compose: $hashed") }// MainViewController.kt fun MainViewController(cryptoProvider: CryptoProvider) = ComposeUIViewController { App(cryptoProvider) } -
На стороне Swift реализуйте функцию хеширования MD5 с помощью чистой библиотеки Swift CryptoKit:
// iosApp/ContentView.swift import CryptoKit class IosCryptoProvider: CryptoProvider { func hashMD5(input: String) -> String { guard let data = input.data(using: .utf8) else { return "failed" } return Insecure.MD5.hash(data: data).description } } -
Передайте реализацию Swift компоненту Kotlin:
// iosApp/ContentView.swift struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { // Inject the Swift implementation into the Kotlin UI entry point MainViewControllerKt.MainViewController(cryptoProvider: IosCryptoProvider()) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} }
-
На стороне Kotlin объявите параметр функции и используйте его там, где это необходимо:
// App.kt @Composable fun App(md5Hasher: (String) -> String) { // Example usage inside your UI val hashed = md5Hasher("Hello, world!") androidx.compose.material3.Text("Compose: $hashed") }// MainViewController.kt fun MainViewController(md5Hasher: (String) -> String) = ComposeUIViewController { App(md5Hasher) } -
На стороне Swift создайте хешер MD5 с помощью библиотеки CryptoKit и передайте его в виде замыкания:
// iosApp/ContentView.swift import CryptoKit import SwiftUI struct ComposeView: UIViewControllerRepresentable { func makeUIViewController(context: Context) -> UIViewController { MainViewControllerKt.MainViewController(md5Hasher: { input in guard let data = input.data(using: .utf8) else { return "failed" } return Insecure.MD5.hash(data: data).description }) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} }
-
На стороне Kotlin объявите интерфейс, функцию, принимающую его, и базовый класс
open, от которого сможет наследоваться реализация Swift:// CryptoProvider.kt interface CryptoProvider { fun hashMD5(input: String): String } fun processHash(provider: CryptoProvider, input: String): String = provider.hashMD5(input) open class SwiftBase -
На стороне Swift унаследуйте класс
SwiftBase, экспортированный в Swift, реализуйте интерфейс с помощью чистой библиотеки Swift CryptoKit и передайте объект обратно в Kotlin:// iosApp/ContentView.swift import CryptoKit final class IosCryptoProvider: SwiftBase, CryptoProvider { func hashMD5(input: String) -> String { guard let data = input.data(using: .utf8) else { return "failed" } return Insecure.MD5.hash(data: data).description } } let provider = IosCryptoProvider() // Calls the Kotlin function, which calls hashMD5() back in Swift print(processHash(provider: provider, input: "Hello, world!"))
Получив объект Swift, Kotlin рассматривает его как реализацию обычного интерфейса Kotlin и вызывает код Swift напрямую.
В более сложном проекте удобнее использовать внедрение зависимостей для передачи реализации Swift обратно в Kotlin. Подробнее см. в разделе Фреймворк для внедрения зависимостей или ознакомьтесь с документацией фреймворка Koin.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/native-lib-import-stability.html