9、Swift协程详解:其他语言异步函数互调用

从异步回调到异步函数

截止目前,我们已经详细探讨了 Swift 协程当中的绝大多数语法设计,这其中最基本也是最重要的就是异步函数。

在异步函数出现之前,我们通常会为函数添加回调来实现异步结果返回,以 Swift 的网络请求库 Alamofire 为例,它的 DataRequest 有这样一个函数:

这个函数有很多参数,不过我们只需要关心最后一个:completionHandler,它是一个闭包,接收一个参数为 AFDataResponse<data></data> 的类型作为请求结果。

从 Swift 5.5 开始,我们可以将其包装成异步函数,添加对结果的异步返回、异常的传播以及对取消响应的支持:

从异步回调到异步函数总是要经过这样一个包装的过程,这个过程实际上并不轻松。因此我们也更希望第三方开发者在提供异步回调的时候同时提供异步函数的版本来方便我们按需使用。

Objective-C 的异步回调

在以前的 iOS SDK 当中,接收形如 completionHandler 这样的回调的 Objective-C 函数有 1000 多个。例如:

这个函数相当于 Swift 的如下函数声明:

如果我们对这些函数一个一个完成包装,那必然会耗费大量的时间和精力。因此,Swift 对接收类似的回调并符合一定条件的 Objective-C 函数自动做了一些转换,以上述 signData 函数为例,可以被自动转换为:

我们来简单分析一下这个转换过程。

那这个转换需要符合什么条件呢?

  • 函数本身和参数回调的返回值均为 void
  • 回调只能被调用一次
  • 函数被显式地用 swift_async 修饰或者隐式地通过参数名来推导,其中支持推导的情况包括:
  • 函数只有一个参数且它的标签为 WithCompletion、WithCompletionHandler、WithCompletionBlock、WithReplyTo、WithReply。
  • 函数有多个参数,且最后一个是回调,并且它的标签为 completion,withCompletion,completionHandler,withCompletionHandler,completionBlock,withCompletionBlock,replyTo,withReplyTo,reply 或者 replyTo。
  • 函数有多个参数,且最后一个参数的标签以一个参数的情况当中列出的标签结尾,最后一个参数是回调。

我们再给一个例子,请大家注意它的函数名:

转换后:

对于以 get 开头的 Objective-C 函数,转换之后函数名当中的 get 被去除了。除此之外其他规则与前面提到的一致。

有了这个转换,很多旧 SDK 当中的 Objective-C 回调函数都可以当成 Swift 的异步函数来调用,可以极大的简化我们的开发流程。

相反地,如果我们定义了 Swift 的异步函数,并且希望在 Objective-C 当中调用,则可以声明成 @objc 异步函数,例如:

GitHubApiAsync 类当中的 listFollowers 函数相当于:

调用 Kotlin 的挂起函数(suspend function)

了解了 Swift 的异步函数如何与 Objective-C 互调用的细节之后,再来看一下 Kotlin 的挂起函数是如何支持被 Swift 调用的。当然这个特性还在实验当中,后续也可能会发生变化。

Kotlin 1.4 开始引入了挂起函数对 Swift 的支持,支持的方式就是讲挂起函数转成回调,例如:

编译之后会生成 Objective-C 头文件,如下:

生成的类名为 SharedGreeting,其中 Shared 是模块名。 __attribute__((swift_name("Greeting"))) 使得这个 Objective-C 类映射到 Swift 当中的名字是 Greeting

我们重点关注一下 greetingAsync 函数,它映射成了下面的回调形式:

Kotlin 挂起函数对于 Objective-C 回调的支持,正好命中了前面讨论的回调自动转换成 Swift 异步函数的条件,因此理论上在 Swift 5.5 当中,我们也可以直接把 Kotlin 的挂起函数当成 Swift 的异步函数去调用:

当然这里还有一些细节的问题。Kotlin 1.5.30 当中也对此做了一点点跟进,在生成的 Objective-C 头文件当中添加了对 _Nullable_result 的支持,这使得 Kotlin 的挂起函数在返回可空类型时,能够正确被转化成返回 optional 类型的 Swift 异步函数,例如:

注意到这个例子的返回值类型声明为 String?,生成的 Objective-C 函数如下:

仔细对比与 greetingAsync 的差异不难发现,返回值的类型在 greetingAsyncNullable 当中被映射成了 NSString * _Nullable_result,而在 greetingAsync 当中则映射成了 NSString * _Nullable。这就不得不提一下 _Nullable_result_Nullable 的差异了,前者可以令转化之后的 Swift 异步函数返回 optional 类型(对应于 Kotlin 的可空类型,nullable type),而后者则返回非 optional 类型(对应于 Kotlin 的不可空类型,nonnull type)。

如果 Kotlin 的挂起函数没有声明为 @Throws,则只有 CancellationException 会被转换为 NSError 抛到 Swift 当中,其他的都会作为严重错误使程序退出,因此如果需要暴露给 Swift 调用,我们通常建议对于可能有异常抛出的 Kotlin 函数添加 @Throws 注解,例如:

这样在 Swift 调用时也可以直接捕获到这个异常:

程序输出如下:

尽管目前 Kotlin 的挂起函数可以被当做 Swift 的异步函数去调用,但 Kotlin 侧仍没有专门仔细地针对 Swift 异步函数调用的场景进行专门的设计和定制。因此像 Swift 侧的取消状态(在 Kotlin 挂起函数中获取 Swift 的 Task 的取消状态)、调度器(Swift 的 actor 以及与 Task 绑定的调度器)、TaskLocal 变量以及 Kotlin 侧挂起函数执行时的调度器、协程上下文等状态都是没有实现传递的。

基于这一点,大家在使用过程中应当尽可能将函数的设计进行简化,避免场景过于复杂而引发令人难以理解的问题。

本文我们探讨了 Swift 协程当中的异步函数(async function)与 Objective-C 的互调用问题,其中介绍了 Objective-C 回调自动映射成 Swift 异步函数的条件和细节,以及 Kotlin 挂起函数对 Swift 异步函数的支持。

Original: https://www.cnblogs.com/strengthen/p/16243907.html
Author: 山青咏芝
Title: 9、Swift协程详解:其他语言异步函数互调用

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/553575/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球