Spec-Zone.ru › Kotlin 1.7

Интеграция с 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 вызывается только тогда, когда потребление памяти становится слишком высоким.

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

© 2010–2022 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