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