Spec-Zone.ru › Kotlin 2

Интеграция со 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. Последовательность событий такова:

  1. Завершается KotlinExample.action. firstKotlinStorage считается «мёртвым», поскольку на него ничто не ссылается, а secondKotlinStorage — нет, поскольку на него ссылается firstSwiftStorage.

  2. Начинается первый цикл GC, и firstKotlinStorage собирается.

  3. На firstSwiftStorage больше нет ссылок, поэтому оно тоже считается «мёртвым», и вызывается deinit.

  4. Начинается второй цикл GC. secondKotlinStorage собирается, поскольку firstSwiftStorage больше на него не ссылается.

  5. Наконец освобождается secondSwiftStorage.

Для сбора этих четырёх объектов требуются два цикла GC, поскольку деинициализация объектов Swift и Objective-C происходит после цикла GC. Это ограничение связано с deinit, который может вызывать произвольный код, в том числе код Kotlin, который нельзя выполнять во время паузы GC.

Циклы сильных ссылок

В цикле сильных ссылок несколько объектов циклически ссылаются друг на друга с помощью сильных ссылок:

A

B

C

Трассирующий сборщик мусора Kotlin и ARC в Objective-C по-разному обрабатывают циклы сильных ссылок. Когда объекты становятся недостижимыми, сборщик мусора Kotlin может корректно освободить такие циклы, а ARC в Objective-C — нет. Поэтому циклы сильных ссылок в объектах Kotlin могут быть освобождены, а циклы сильных ссылок в объектах Swift/Objective-C — нет.

Рассмотрим случай, когда цикл сильных ссылок содержит объекты Objective-C и Kotlin:

Kotlin.A

ObjC.B

Здесь сочетаются модели управления памятью Kotlin и Objective-C, которые не могут совместно обрабатывать (освобождать) циклы сильных ссылок. Это означает, что если в графе объектов присутствует хотя бы один объект Objective-C, цикл сильных ссылок во всём графе освободить нельзя, и разорвать цикл со стороны Kotlin невозможно.

К сожалению, в настоящее время нет специальных инструментов для автоматического обнаружения циклов сильных ссылок в коде Kotlin/Native. Чтобы избежать таких циклов, используйте слабые или бесхозные ссылки.

Поддержка фонового состояния и расширений приложений

Текущий менеджер памяти по умолчанию не отслеживает состояние приложения и не поддерживает расширения приложений из коробки.

Это означает, что менеджер памяти не корректирует соответствующим образом поведение GC, что в некоторых случаях может быть вредно. Чтобы изменить это поведение, добавьте следующий экспериментальный бинарный параметр в gradle.properties:

kotlin.native.binary.appStateTracking=enabled

Этот параметр отключает запуск сборщика мусора по таймеру, когда приложение находится в фоновом режиме, поэтому GC запускается только при чрезмерном потреблении памяти.

Что дальше

Узнайте больше о взаимодействии Swift/Objective-C.

17 апреля 2025 г.
Управление памятью в Kotlin/NativeПереход на новый менеджер памяти

© 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

Spec-Zone.ru

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