Kotlin-Swift 协程的互调用

Thu Aug 07 2025

Kotlin的协程是结构化并发, 他是底层是CPS,支持协作式的取消。 Swift的协程和Kotlin非常相似。

基于回调的协程函数

Kotlin可以通过suspendCoroutine函数会尝试挂起协程, 取得Continuation实例。 如果在block中直接调用了continuation.resume或continuation.resumeWithException,则该suspend函数不会挂起,而是直接返回。

如果没有调用,则会返回了一个COROUTINE_SUSPENDED状态位,标记函数已经挂起。通常这种情况是函数调用栈切换, 例如切线程, 或者是讲Continuation实例保存在别处,作为callback将来调用。

suspend fun foo(): Int = suspendCoroutine { continuation ->
// Case1: 直接返回
    continuation.resume(1)
 
// Case2: 协程挂起
    thread {
      continuation.resumeWithException(1)
    }
}

Swift也类似,他有两个函数, 一个是有异常抛出的情况用withCheckedThrowingContinuation, 没有的话用withCheckedContinuation。

    func bar() async -> Int {
        return await withCheckedContinuation { countinuation in
            countinuation.resume(returning: 1)
        }
    }
 
    func foo() async throws -> Int {
        return try await withCheckedThrowingContinuation { countinuation in
            countinuation.resume(throwing: NSError(domain: "", code: 1))
        }
    }

协程的创建

Kotlin创建协程需要一个CoroutineScope, 这个scope提供了一个CoroutineContext, 他可以取消在该Scope中启动的协程。 启动的子协程也可以通过这个context获取启动时注入的实例, 算是一种DI机制。

用户可以自行创建一个CoroutineScope, 这是需要用户自己管理scope的生命周期。 通常平台会给提供CoroutineScope, 比如android上有ViewModelScope, LifecycleScope。

Kotlin还有一个GloableScope, 这个就相当于没有生命周期的scope, 启动的协程不会别取消。

GloableScope.launch {
  foo()
}

Swift启动协程使用Task构造器, 这种创建Task的方式, 和kotlin的GloableScope类似, 都是top level的, 没有生命周期管理。

Task {
  try await foo()
}

协程的取消

Kotlin协程的取消是协作式的,suspendCancellableCoroutine函数可以相应取消。 invokeOnCancellation函数注册一个callback, 当协程被取消时, callback被调用, 这时需要回收打开的资源, 避免造成泄漏问题。 之后,CancellationException会经过一次Dispatcher的dispach, 抛出CancellationException异常。

suspend fun foo() = suspendCancellableCoroutine { continuation ->
  continuation.invokeOnCancellation {
    // 回收资源
  }
 
  continuation.resume(1)
}

Swift使用withTaskCancellationHandler相应task的取消。

    func foo() async throws -> Int {
        return try await withTaskCancellationHandler {
            await withCheckedContinuation { countinuation in
                countinuation.resume(returning: 1)
            }
        } onCancel: {
            // 回收资源
        }
    }

Kotlin协程与Swift协程互调用

swift调用kotlin的suspend函数

Kotlin Native目前只可以导出objc的头文件。 下面suspend的foo函数,转换成了fooWithCompletionHandler的objc函数。

suspend fun foo(): String
- (void)fooWithCompletionHandler:(void (^)(NSString * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("foo(completionHandler:)")));

这个objc方法是符合下面文档所说的Swift objc异步函数互调用的格式的, 因此swift代码把这个函数看作func foo() async throws -> String,在swift中调用。

Task {
  try await foo()
}

kotlin的suspend函数,如果desugar一下,就是下面这种形式。

Object foo(@NotNull Continuation $completion)

这里猜测一下,fooWithCompletionHandler调用到kotlin时,这个函数很可能这样套了一层:(实际debug了一下, foo函数中的取得的coroutineContext就是EmptyCoroutineContext)

fun fooWithCompletionHandler(callback: (String?, Throwable?) -> Unit) {
    suspend {
        foo()
    }.startCoroutine(
        object : Continuation<String> {
            override val context: CoroutineContext
                get() = EmptyCoroutineContext
 
            override fun resumeWith(result: Result<String>) {
                if (result.isSuccess) {
                    callback(result.getOrNull(), null)
                } else {
                    callback("", result.exceptionOrNull())
                }
            }
        },
    )
}

这样的话, foo函数启动调用时, 所在的协程中是没有调度器的,自然也就不支持取消。

kotlin调用swift的协程函数

fooWithCompletionHandler这个protocal,swift实现只需要callback风格和async风格两种二选一就可以了。

- (void)fooWithCompletionHandler_:(void (^)(NSString * _Nullable, NSError * _Nullable))completionHandler __attribute__((swift_name("foo(completionHandler_:)")));
    func foo(completionHandler_ completionHandler: @escaping (String?, (any Error)?) -> Void) {
        <#code#>
    }
    
    func foo() async throws -> String {
        <#code#>
    }
    

如果只实现了async风格的函数, objc调用时是什么行为呢? 。。。搞不大懂。

参考: https://forums.swift.org/t/concurrency-interoperability-with-objective-c/41616

← Back to home