Skip to content

内存模型与 Swift 互操作

源:Kotlin/Native 内存管理官方文档

理解 Kotlin Multiplatform (KMP) 的内存模型,是编写高性能、无崩溃跨平台代码的关键。特别是对于 iOS 开发者,理解 Kotlin 的垃圾回收 (GC) 如何与 Swift 的自动引用计数 (ARC) 共存至关重要。

内存模型的演进:从“冻结”到“自由”

旧内存模型 (已废弃)

在 Kotlin 1.9 之前,Native 目标使用非常严格的内存规则:

  • 对象隔离: 跨线程共享的对象必须是“冻结”的(freeze())。
  • 不可变性: 冻结后的对象变为不可变,任何修改都会抛出 InvalidMutabilityException
  • 痛点: 开发者需要花费大量时间处理对象冻结,代码极其臃肿。

新内存模型 (默认开启)

从 Kotlin 1.9.20 开始(并在 Kotlin 2.3.0 中进一步优化),所有平台统一采用现代内存管理器

  • 完全线程共享: 就像在 JVM 上一样,您可以在不同线程间自由读写对象。
  • 后台 GC: Kotlin/Native 拥有独立的垃圾回收器,能在后台清理不再使用的对象。
  • 无缝衔接: 开发者不再需要手动调用 freeze()

Kotlin GC vs Swift ARC

当 Kotlin 暴露给 Swift 时

,会发生以下奇妙的化学反应:

  • Swift 视角: Kotlin 对象看起来就像一个普通的 Swift 类实例,遵循 ARC 规则。
  • Kotlin 视角: 只要 Swift 侧还持有一个 Kotlin 对象的引用,该对象在 Kotlin 堆中就不会被回收。
  • 循环引用: ⚠️ 这是唯一的风险点。如果一个 Kotlin 对象持有一个 Swift 对象,而该 Swift 对象又持有回这个 Kotlin 对象,GC 与 ARC 的交叉引用可能导致内存泄漏。

解决方案

在 Swift 侧使用 weak 引用,或者在 Kotlin 侧使用 WeakReference 来打破循环。

调用协程:Suspend 函数转换

Swift 侧调用 Kotlin

suspend 函数时,Kotlin 编译器会自动将其转换为带回调的形式,或(在最新版中)支持 Swift Async/Await

kotlin
class UserRepository {
    suspend fun getUserName(): String {
        delay(1000)
        return "Virogu"
    }
}
swift
// 需要使用 SKIE 插件或最新编译器特性
let repo = UserRepository()
Task {
    do {
        let name = try await repo.getUserName()
        print("User: \(name)")
    } catch {
        print("Error: \(error)")
    }
}

性能调优:减少跨界开销

跨平台调用

(Crossing the boundary)虽然方便,但并非完全免费:

  1. 数据转换: 传递基础类型(Int, Double)极快。传递复杂的 ListMap 会触发底层的类型转换和复制。
  2. 调用频率: 避免在每一帧(如绘图回调)中频繁跨界调用。建议将大块逻辑放在 Kotlin 侧处理,一次性返回结果。
  3. 大对象处理: 如果要传递巨型 JSON 或图片字节流,考虑直接传递原始指针(ByteArray 的 C 指针),以避免不必要的内存拷贝。