Интеграция с iOS
Интеграция сборщика мусора 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. Вот последовательность событий:
KotlinExample.actionзавершается.firstKotlinStorageсчитается "мертвым", потому что на него ничего не ссылается, в то время какsecondKotlinStorageне считается, так как на него ссылаетсяfirstSwiftStorage.Начинается первый цикл GC, и
firstKotlinStorageсобирается.Ссылки на
firstSwiftStorage, отсутствуют, поэтому он тоже "мёртв", и вызываетсяdeinit.Начинается второй цикл GC.
secondKotlinStorageсобирается, так какfirstSwiftStorageбольше не ссылается на него.secondSwiftStorageнаконец освобождается.
Для сбора этих четырёх объектов требуется два цикла GC, потому что деинициализация Swift и Objective-C объектов происходит после цикла GC. Это ограничение обусловлено deinit, которое может вызывать произвольный код, в том числе Kotlin-код, который нельзя выполнить во время паузы GC.
Поддержка фонового состояния и расширений приложения
Текущий менеджер памяти по умолчанию не отслеживает состояние приложения и не интегрируется с расширениями приложения.
Это означает, что менеджер памяти не корректирует поведение GC соответствующим образом, что может быть вредным в некоторых случаях. Чтобы изменить это поведение, добавьте следующий экспериментальный бинарный параметр в свой gradle.properties:
kotlin.native.binary.appStateTracking=enabled
Это отключает вызов сборщика мусора на основе таймера, когда приложение находится в фоновом режиме, поэтому GC вызывается только тогда, когда потребление памяти становится слишком высоким.
© 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