Spec-Zone.ru › Kotlin 1.8

Интеграция с iOS

Эта страница описывает новый менеджер памяти, включённый по умолчанию с Kotlin 1.7.20. Обратитесь к нашему руководству по миграции, чтобы переместить свои проекты с устаревшего менеджера памяти.

Интеграция сборщика мусора Kotlin/Native с ARC Swift/Objective-C проходит бесшовно и, как правило, не требует дополнительных действий. Узнайте больше о взаимодействии Swift/Objective-C.

Однако, есть некоторые особенности, которые следует учитывать:

Потоки

Деинициализаторы

Деинициализация Swift/Objective-C объектов и объектов, на которые они ссылаются, вызывается в другом потоке, если эти объекты пересекают границы взаимодействия с Kotlin/Native, например:

// 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 <NSThread: 0x600003b9b900>{number = 7, name = (null)}

Обработчики завершения

При вызове 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)}

Вызов Kotlin-подвешенных функций

Менеджер памяти Kotlin/Native имеет ограничение на вызов Kotlin-подвешенных функций из Swift и Objective-C из потоков, отличных от главного.

Это ограничение изначально было введено в устаревшем менеджере памяти из-за случаев, когда код рассылал продолжение для возобновления в исходном потоке. Если этот поток не имел поддерживаемой очереди событий, задача никогда не выполнялась, а сопрограмма никогда не возобновлялась.

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

kotlin.native.binary.objcExportSuspendFunctionLaunchThreadRestriction=none

Сбор мусора и жизненный цикл

Освобождение объектов

Объект освобождается только во время сбора мусора. Это относится к 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.

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

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

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

kotlin.native.binary.appStateTracking=enabled

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

Последнее изменение: 10 января 2023
Управление памятью Kotlin/Native Миграция на новый менеджер памяти

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

Spec-Zone.ru

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