当前位置: 首页 > article >正文

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

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


http://www.kler.cn/a/288999.html

相关文章:

  • 解析在OceanBase创建分区的常见问题|OceanBase 用户问题精粹
  • Tomcat负载均衡全解析
  • Debian11 安装MYSQL8 签名错误
  • SPL06 基于stm32F103 HAL库驱动(软件模拟IIC)
  • MySQL表名传参SP
  • java全栈day20--Web后端实战(Mybatis基础2)
  • 聊聊随机测试和猴子测试
  • Python参数传递的艺术:解锁编程灵活性的秘密武器
  • uniapp写的一个年月日时分秒时间选择功能
  • 【数据结构初阶】——栈和队列
  • 求三元组中可能出现的最小距离
  • RabbitMQ练习(Routing)
  • 使用COAP和MQTT协议的多协议方法开发的用于机器人手术的自动医疗物联网系统
  • vue3+ts 实现模板表格文件下载~
  • pikachu文件包含漏洞靶场攻略
  • 密钥分发与公钥认证:保障网络通信的安全
  • MySQL入门学习-MySQL的连接查询
  • MySQL——事务与存储过程(二)存储过程的创建(4)光标的使用
  • 【Linux学习笔记】protobuf相关操作
  • 数仓基础(九):各大公司实时数仓实践
  • Go锁 详解
  • k8s-使用Network Policies实现网络隔离
  • (二)、软硬件全开源智能手表,可全面高精度采集生命体征数据,进行健康检测。(HealthyPi Move)
  • 【Java中的三元运算符】
  • 书法图片自动扣字的批处理
  • leecode 31.下一个排列(Golang)