Интеграция со Swift/Objective-C ARC
Kotlin и Objective-C используют разные стратегии управления памятью. В Kotlin есть трассирующий сборщик мусора, а Objective-C использует автоматический подсчёт ссылок (ARC).
Интеграция этих стратегий обычно выполняется без проблем и, как правило, не требует дополнительных действий. Однако следует учитывать некоторые особенности:
Потоки
Деинициализаторы
Деинициализация объектов Swift/Objective-C и объектов, на которые они ссылаются, выполняется в главном потоке, если эти объекты переданы в Kotlin в главном потоке. Например:
// Kotlin
class KotlinExample {
fun action(arg: Any) {
println(arg)
}
}
// Swift
class SwiftExample {
init() {
print("init on \(Thread.current)")
}
deinit {
print("deinit on \(Thread.current)")
}
}
func test() {
KotlinExample().action(arg: SwiftExample())
}
Результат:
init on <_NSMainThread: 0x600003bc0000>{number = 1, name = main}
shared.SwiftExample
deinit on <_NSMainThread: 0x600003bc0000>{number = 1, name = main}
Деинициализация объектов Swift/Objective-C выполняется в специальном потоке сборщика мусора, а не в главном потоке, если:
Объекты Swift/Objective-C переданы в Kotlin в потоке, отличном от главного.
Главная очередь диспетчеризации не обрабатывается.
Чтобы явно вызывать деинициализацию в специальном потоке сборщика мусора, задайте kotlin.native.binary.objcDisposeOnMain=false в gradle.properties. Этот параметр включает деинициализацию в специальном потоке сборщика мусора, даже если объекты Swift/Objective-C были переданы в Kotlin в главном потоке.
Специальный поток сборщика мусора соответствует требованиям среды выполнения Objective-C: у него есть цикл выполнения, и он очищает пулы автосоздания объектов.
Обработчики завершения
При вызове приостанавливающих функций Kotlin из Swift обработчики завершения могут вызываться в потоках, отличных от главного. Например:
// Kotlin
// coroutineScope, launch, and delay are from kotlinx.coroutines
suspend fun asyncFunctionExample() = coroutineScope {
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
// Swift
func test() {
print("Running test on \(Thread.current)")
PlatformKt.asyncFunctionExample(completionHandler: { _ in
print("Running completion handler on \(Thread.current)")
})
}
Результат:
Running test on <_NSMainThread: 0x600001b100c0>{number = 1, name = main}
Hello
World!
Running completion handler on <NSThread: 0x600001b45bc0>{number = 7, name = (null)}
Сборка мусора и жизненный цикл
Освобождение объектов
Объект освобождается только во время сборки мусора. Это относится к объектам Swift/Objective-C, пересекающим границы взаимодействия с Kotlin/Native. Например:
// Kotlin
class KotlinExample {
fun action(arg: Any) {
println(arg)
}
}
// Swift
class SwiftExample {
deinit {
print("SwiftExample deinit")
}
}
func test() {
swiftTest()
kotlinTest()
}
func swiftTest() {
print(SwiftExample())
print("swiftTestFinished")
}
func kotlinTest() {
KotlinExample().action(arg: SwiftExample())
print("kotlinTest finished")
}
Результат:
shared.SwiftExample SwiftExample deinit swiftTestFinished shared.SwiftExample kotlinTest finished SwiftExample deinit
Жизненный цикл объектов Objective-C
Объекты Objective-C могут жить дольше, чем должны, что иногда приводит к проблемам с производительностью. Например, в длительном цикле на каждой итерации создаётся несколько временных объектов, пересекающих границу взаимодействия Swift/Objective-C.
В журналах GC указано количество стабильных ссылок в корневом наборе. Если это число продолжает расти, это может указывать на то, что объекты Swift/Objective-C не освобождаются своевременно. В таком случае попробуйте поместить блок autoreleasepool вокруг тел циклов, выполняющих вызовы взаимодействия:
// Kotlin
fun growingMemoryUsage() {
repeat(Int.MAX_VALUE) {
NSLog("$it\n")
}
}
fun steadyMemoryUsage() {
repeat(Int.MAX_VALUE) {
autoreleasepool {
NSLog("$it\n")
}
}
}
Сборка мусора для цепочек объектов Swift и Kotlin
Рассмотрим следующий пример:
// Kotlin
interface Storage {
fun store(arg: Any)
}
class KotlinStorage(var field: Any? = null) : Storage {
override fun store(arg: Any) {
field = arg
}
}
class KotlinExample {
fun action(firstSwiftStorage: Storage, secondSwiftStorage: Storage) {
// Here, we create the following chain:
// firstKotlinStorage -> firstSwiftStorage -> secondKotlinStorage -> secondSwiftStorage.
val firstKotlinStorage = KotlinStorage()
firstKotlinStorage.store(firstSwiftStorage)
val secondKotlinStorage = KotlinStorage()
firstSwiftStorage.store(secondKotlinStorage)
secondKotlinStorage.store(secondSwiftStorage)
}
}
// Swift
class SwiftStorage : Storage {
let name: String
var field: Any? = nil
init(_ name: String) {
self.name = name
}
func store(arg: Any) {
field = arg
}
deinit {
print("deinit SwiftStorage \(name)")
}
}
func test() {
KotlinExample().action(
firstSwiftStorage: SwiftStorage("first"),
secondSwiftStorage: SwiftStorage("second")
)
}
Между появлением в журнале сообщений "deinit SwiftStorage first" и "deinit SwiftStorage second" проходит некоторое время. Причина в том, что firstKotlinStorage и secondKotlinStorage собираются в разных циклах GC. Последовательность событий такова:
Завершается
KotlinExample.action.firstKotlinStorageсчитается «мёртвым», поскольку на него ничто не ссылается, аsecondKotlinStorage— нет, поскольку на него ссылаетсяfirstSwiftStorage.Начинается первый цикл GC, и
firstKotlinStorageсобирается.На
firstSwiftStorageбольше нет ссылок, поэтому оно тоже считается «мёртвым», и вызываетсяdeinit.Начинается второй цикл GC.
secondKotlinStorageсобирается, посколькуfirstSwiftStorageбольше на него не ссылается.Наконец освобождается
secondSwiftStorage.
Для сбора этих четырёх объектов требуются два цикла GC, поскольку деинициализация объектов Swift и Objective-C происходит после цикла GC. Это ограничение связано с deinit, который может вызывать произвольный код, в том числе код Kotlin, который нельзя выполнять во время паузы GC.
Циклы сильных ссылок
В цикле сильных ссылок несколько объектов циклически ссылаются друг на друга с помощью сильных ссылок:
Трассирующий сборщик мусора Kotlin и ARC в Objective-C по-разному обрабатывают циклы сильных ссылок. Когда объекты становятся недостижимыми, сборщик мусора Kotlin может корректно освободить такие циклы, а ARC в Objective-C — нет. Поэтому циклы сильных ссылок в объектах Kotlin могут быть освобождены, а циклы сильных ссылок в объектах Swift/Objective-C — нет.
Рассмотрим случай, когда цикл сильных ссылок содержит объекты Objective-C и Kotlin:
Здесь сочетаются модели управления памятью Kotlin и Objective-C, которые не могут совместно обрабатывать (освобождать) циклы сильных ссылок. Это означает, что если в графе объектов присутствует хотя бы один объект Objective-C, цикл сильных ссылок во всём графе освободить нельзя, и разорвать цикл со стороны Kotlin невозможно.
К сожалению, в настоящее время нет специальных инструментов для автоматического обнаружения циклов сильных ссылок в коде Kotlin/Native. Чтобы избежать таких циклов, используйте слабые или бесхозные ссылки.
Поддержка фонового состояния и расширений приложений
Текущий менеджер памяти по умолчанию не отслеживает состояние приложения и не поддерживает расширения приложений из коробки.
Это означает, что менеджер памяти не корректирует соответствующим образом поведение GC, что в некоторых случаях может быть вредно. Чтобы изменить это поведение, добавьте следующий экспериментальный бинарный параметр в gradle.properties:
kotlin.native.binary.appStateTracking=enabled
Этот параметр отключает запуск сборщика мусора по таймеру, когда приложение находится в фоновом режиме, поэтому GC запускается только при чрезмерном потреблении памяти.
Что дальше
Узнайте больше о взаимодействии Swift/Objective-C.
© 2010–2026 JetBrains s.r.o. and Kotlin Programming Language contributors
Licensed under the Apache License, Version 2.0.
https://kotlinlang.org/docs/native-arc-integration.html