初识 async/await

WWDC21 推出了 async/await,特意去百度了一下:

微软在发布 VS2012 的同时推出了C# 5.0,其中包含了async和await。

Swift 也在进化,WWDC21 推出的 swift 5.5 也终于加入了 async/await 特性。下面是对 Meet async/await in Swift 的整理。如果有不恰当的地方,欢迎评论区交流。

如上图,列表中每一行左侧的小图标是从服务端下载下来并渲染到 UI 界面上来的。一般情况下远端图片下载渲染分以下四个步骤:
1、通过图片的地址 URL 地址创建一个 URLRequest 的对象;
2、以上一步创建的 URLRequest 对象为参,通过 URLSession 创建一个 task 任务用于请求远端的图片数据;
3、等待,异步获取 UIImage 的 data 数据, 然后将 data 通过 UIImage 的 initWithData 方法转化成 image 对象;
4、将该 image 对象渲染到 UI 界面;

IMG_0517.PNG

一个简单的异步获取数据并在 completion handler 中处理返回的数据,每位 iOS 开发者对这个过程并不陌生。其整的实现代码如下

IMG_0525.PNG

很熟悉的味道吧!

二十行代码左右,通常我们都会去这么实现或采用类似的实现方式。所谓习惯成自然,也就没觉着这样有什么不好。但可不可以更简洁点呢?

上面的写法不简洁吗?

是的!
相比 async/await 的实现方式,上面的代码有以下几个问题:
1、为了向通知外界返回的结果,completion 回调写了 5 次,而且很容易在两个guardelse 执行语句中漏写;
2、不直观,函数体若按上到下的顺序执行比较符合人们的阅读习惯,而且很清楚的表达了代码的意图。但这里图片的下载是异步的, dataTask 的尾随闭包里面的代码其实是后执行的;
3、代码多,容易乱,不够简洁;

现在 Swift 5.5 引入 async/await 特性。下面来使用 async/await 语法来实现上述功能。

分析一下这个实现:
先来看函数定义这一行,fetchThumbnail 参数的右括号后面紧跟了一个 async 关键字,表示该函数为 async 函数。async 后面则跟了个 throw 关键字,表示该函数可能会抛出错误。

函数体中第二行在获取图片时使用了 await 关键字,表示这是一个需要等待的操作,因为图片的下载因网络好坏需要不确定的时间。此时函数执行到这里会暂停(suspend),但不会阻塞程序的执行,系统会接管该函数所在的线程去继续执行其它的任务。待图片下载完成之后,系统又会回到这个暂停的地方,该函数会 resume 从而继续执行下面的函数语句

接下来是处理返回数据。若返回的数据的 statusCode 不等于 200 ,说明请求不成功,使用 throw 抛出错误。反之将返回的数据 data 转化为 UIImage 对象。在数据转换过程中如果发生异常,同样由 throw 抛出错误,否则就返回处理好的 thumnail 对象供渲染使用。

至此, async/await 版的代码完成了上面传统的 completion handler 回调函数完成的图片下载。代码量简洁,逻辑更清晰,代码可读性幅提高!都忍不住想来一句:good job !

回过头再去仔细看一下这个函数实现。倒数第二行乍一看上去不是一个异步函数,但仍然使用了 await 关键字,这不科学!其实不然,这里的 thmbnail 其实是一个 async 类型的属性(property)。

注意:仅 read-only 类型的 property 才可以是标记为 async。

不仅 functionpropertyinitializer 也可以标记为 async。那么还有没有其它的呢?有,下面有请 AyncSequence 登场!

AsyncSequence 是一个可以异步获取其元素的一个序列。由于其每一个元素的获取都是异步的,所以依次获取其中的每一个元素都是 awaitable 的,需要使用 await 关键字进行标识。

综上,有很多地方你可以使用 await 关键字,await 也表示一个 async 标识的函数执行到这里时候会暂停 suspend。那这里的暂停 suspend 意味着什么呢?

通常情况下,当我们调用一个函数的时候,函数体的执行在执行完之前会一直占用当前的线程,直到函数体执行完毕之后当前线程才能去执行其它的任务。而 async 函数就不一样了,它具有 暂停 suspend 的能力。当暂停时,它就会让出当前线程给系统,由系统去决定当前线程去执行其它任务。而且 async 函数可以暂停 suspend 多次。当标识为 await 的任务完成之后,系统又会重新回到原来暂停的地方,原来的函数被 resume 接着执行函数体下面的代码。需要注意的是,被 resume 的函数所处的线程可能与之前 suspend 时所处的线程不是同一个线程。

async 函数的执行流程

async 函数的执行流程

理论和示例都看过了,问题来了:怎样将现有的代码切换为 async/await 呢?

先从现代软件编码的一个重点代码测试开始。我们想像测试同步代码一样方便的去测试异步代码,于是 async/await 就派上用场了!

下面是一个通常情况下的测试异步代码的一个示例

使用 async/await 特性之后

不仅如此,当前很多的系统库 API 也都支持了 async/await 这一特性。
比如

代理方法中也有不少 completion handler 语法,当然我们没有忘记。同样代理方法你也可以选择使用 async/await 特性的 API。

到这里,你所看到的所有异步函数都是 Swift 帮你实现的,你只要使用就行了。但总有一些情形需要你自己去搞定异步函数。于是就有了 continuation 。借助 continuation,你可以灵活的自己去决定异步函数 resume 的时机和位置。

如上,借助 withCheckedThrowingContinuation 你可以向上面这样使用 continuations 。

使用 continuation 需要注意两点:
1、continuation 不能被丢弃,必须要执行相关操作以唤醒之前 suspend 的异步函数;
2、continuationresume 函数在一个分支条件上只能调用一次;

Thanks for reading!

你可能感兴趣的:(初识 async/await)