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了,还是来看看下一篇文章吧。

最后,希望能够帮助到有需要的朋友,如果觉得有帮助,还望点个赞,添加个关注,笔者也会不断地努力,写出更多更好用的文章。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值