Swift concurrency 5 — async let的理解与使用
在前面的文章中,我们介绍过async/await
这两个关键字,也了解了异步方法,在一个Task
中,多个加了await
的异步方法是顺序执行的,一个接着一个,这个在有些情况下是很好的,比如用户登录,获取token,再获取info信息等。但是有些时候如果有几个不相干的请求想同时都发送出去,然后等他们一起都回来了,再统一处理剩余逻辑,那么此时就要用到async let
了。
下面先来看一组代码:
struct AsyncLetDemo: View {
@State private var images: [UIImage] = []
let columns = [GridItem(.flexible()), GridItem(.flexible())]
let url = URL(string: "https://picsum.photos/300")!
var body: some View {
ScrollView {
LazyVGrid(columns: columns) {
ForEach(images, id: \.self) { image in
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(height: 150)
}
}
}
.onAppear {
Task {
do {
let image1 = try await fetchImage()
self.images.append(image1)
let image2 = try await fetchImage()
self.images.append(image2)
let image3 = try await fetchImage()
self.images.append(image3)
let image4 = try await fetchImage()
self.images.append(image4)
} catch {
print("\(error.localizedDescription)")
}
}
}
}
func fetchImage() async throws -> UIImage {
do {
let (data, _) = try await URLSession.shared.data(from: url, delegate: nil)
if let image = UIImage(data: data) {
return image
} else {
throw URLError(.badURL)
}
} catch {
throw error
}
}
}
在上面的代码中,我们通过async/await
结合Task
的方式请求了4个图片,并在LazyVGrid
中显示,按照之前说的逻辑,这4张图片应该会按照顺序显示出来。
上一篇文章中说过同一个Task
中的异步任务是按照顺序执行的,那么不同的Task
是一起执行的,那么稍微修改一下代码:
.onAppear {
Task {
do {
let image1 = try await fetchImage()
self.images.append(image1)
} catch {
print("\(error.localizedDescription)")
}
}
Task {
do {
let image2 = try await fetchImage()
self.images.append(image2)
} catch {
print("\(error.localizedDescription)")
}
}
Task {
do {
let image3 = try await fetchImage()
self.images.append(image3)
} catch {
print("\(error.localizedDescription)")
}
}
Task {
do {
let image4 = try await fetchImage()
self.images.append(image4)
} catch {
print("\(error.localizedDescription)")
}
}
}
修改成这样,按理说这些图片应该是基本上同时请求回来了,看下效果:
这回效果好多了,但是代码确实是多了很多,如果这样写代码,可能会被骂,哈哈。
下面就来看看今天的重点,async let
。
async let fetchImage1 = fetchImage()
async let fetchImage2 = fetchImage()
async let fetchImage3 = fetchImage()
async let fetchImage4 = fetchImage()
fetchImage()
是我们的请求图片的方法,采用async let
定义变量并持有这个方法,比如上面代码。定义完成后fetchImage()
是没有立即调用的。而它们的调用也是非常有意思。
let (image1, image2, image3, image4) = await (try fetchImage1, try fetchImage2, try fetchImage3, try fetchImage4)
上面代码等号左边括号内接收返回值,等号右边括号内调用方法,因为fetchImage()
是有throws
的,所以的加上try
,另外这是个async
修饰的异步方法,所以得加await
,这里面await
只需要一个即可。
当上面4个请求都返回后代码才会继续往下走。通过async let
我们可以让一组异步方法同时去执行,并且当所有异步方法都返回结果后,程序再继续往下走。
完整代码如下:
.onAppear {
Task {
do {
async let fetchImage1 = fetchImage()
async let fetchImage2 = fetchImage()
async let fetchImage3 = fetchImage()
async let fetchImage4 = fetchImage()
let (image1, image2, image3, image4) = await (try fetchImage1, try fetchImage2, try fetchImage3, try fetchImage4)
self.images.append(contentsOf: [image1, image2, image3, image4])
} catch {
print("\(error.localizedDescription)")
}
}
}
上面的代码是比较理想状态下的,如果某个请求错误了,抛出了异常怎么办,比如下面,我们加了一个方法,使其抛出异常。
func fetchImageWithError() async throws -> UIImage {
do {
throw URLError(.badURL)
} catch {
throw error
}
}
调用的地方如下:
.onAppear {
Task {
do {
async let fetchImage1 = fetchImage()
async let fetchImage2 = fetchImage()
async let fetchImage3 = fetchImage()
async let fetchImageWithError = fetchImageWithError()
let (image1, image2, image3, image4) = await (try fetchImage1, try fetchImage2, try fetchImage3, try fetchImageWithError)
self.images.append(contentsOf: [image1, image2, image3, image4])
} catch {
print("\(error.localizedDescription)")
}
}
}
上面代码中fetchImageWithError
肯定会抛异常了,那么代码就直接到catch
闭包里面了,其他三个正常返回的请求也就失效了。
从效果图可以看出,控制台有错误打印出来,UI上并没有显示出成功的三个图片。
解决办法也比较简单,在fetchImageWithError
方法前将try
改成try?
,忽略它的异常报错,这样也会引起一个问题,那么就是在向数组里添加图片的时候会报错,因为改成try?
修饰的fetchImageWithError
方法返回了一个可选的UIImage
,所以我们需要判断这个UIImage
是否存在,然后在加入图片数组中。
.onAppear {
Task {
do {
async let fetchImage1 = fetchImage()
async let fetchImage2 = fetchImage()
async let fetchImage3 = fetchImage()
async let fetchImageWithError = fetchImageWithError()
let (image1, image2, image3, image4) = await (try fetchImage1, try fetchImage2, try fetchImage3, try? fetchImageWithError)
self.images.append(contentsOf: [image1, image2, image3, image4 ?? UIImage()])
} catch {
print("\(error.localizedDescription)")
}
}
}
上面代码中再给图片数组添加图片的时候,第四个可选的图片采用了??
设置了缺省值,也可以不这样做。
如果你的异步方法里面没有throws
关键字,那么在调用的时候前面就不需要加try
了。
总体来说async let
的用法还是比较简单的,可以让我们将多个异步方法同时发送出去,并在所有结果都返回后再执行后续的代码,这在项目中的某些场景还是非常实用的。感兴趣的小伙伴赶紧用起来吧。
最后在说一句,如果你有很多歌,比如几十个异步请求同时发送出去,然后等待统一返回结果,那不建议用async let
了,还是来看看下一篇文章吧。
最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。