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

Swift concurrency 3 — 三种异步方式(@escaping closure, Combine, async/await)

直到现在为止,如果我们想要异步请求数据,应该说至少有三种方式:

  1. 传统的通过闭包(@escaping closure)方式回调处理。
  2. 通过Combine的发布者订阅者机制。
  3. 通过async/await组合的方式。

采用哪种方式,还得因项目而异,本文将对这三种方式做一个简单的总结,以及代码示例。
下面就以下载一个网络图片为例。

首先还是要先定义一个界面和一个对应的ViewModel:

struct DownloadImageDemo: View {@StateObject private var viewModel = DownloadImageDemoViewModel()var body: some View {ZStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {}}
}
class DownloadImageDemoViewModel: ObservableObject {@Published var image: UIImage?var downloader: ImageDownloader = ImageDownloader()func fetchImageWithEscapingClosure() {}func fetchImageWithCombine() {}func fetchImageWithAsnynAndAwait() {}
}

在ViewModel中我们先定义了三个方法,分别用于处理不同的请求,另外为了更加符合项目,将图片下载逻辑放到一个我们模拟的网络层处理ImageDownloader

class ImageDownloader {let url = URL(string: "https://picsum.photos/200")!
}

至此基本的代码逻辑已经完成,下面重点看一下下载部分的代码,这部分代码统一在ImageDownloader中处理。

escaping closure方式

class ImageDownloader {let url = URL(string: "https://picsum.photos/200")!func handleResponse(data: Data?, response: URLResponse?) -> UIImage? {guard let data,let image = UIImage(data: data),let response = response as? HTTPURLResponse,response.statusCode >= 200 && response.statusCode < 300 else {return nil}return image}func fetchImageWithEscapingClosure(_ completion: @escaping (UIImage?, Error?) -> Void) {URLSession.shared.dataTask(with: URLRequest(url: url)) { [weak self] data, response, error inlet image = self?.handleResponse(data: data, response: response)completion(image, error)}.resume()}
}

在上面的代码中,我们在ImageDownloader中定义了fetchImageWithEscapingClosure方法,其参数为一个逃逸闭包,用于返回网络请求的结果,想必都不陌生了。
为了简化代码,这里面将错误处理单独拿出来放到handleResponse中处理,并返回一个可选的UIImage

在ViewModel中的方法中调用如下:

func fetchImageWithEscapingClosure() {downloader.fetchImageWithEscapingClosure { [weak self] image, _ inself?.image = image}
}

SwiftUI界面调用如下:

struct DownloadImageDemo: View {@StateObject private var viewModel = DownloadImageDemoViewModel()var body: some View {ZStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {viewModel.fetchImageWithEscapingClosure()}}
}

Combine方式

首先在ImageDownloader中定义一个方法,具体如下:

func fetchImageWithCombine() -> AnyPublisher<UIImage? ,Error> {URLSession.shared.dataTaskPublisher(for: url).map(handleResponse).mapError({ $0 }).eraseToAnyPublisher()
}

该方法返回了一个AnyPublisher类型,并定义好泛型类型,以便调用的地方订阅。
URLSession.shared.dataTaskPublisher方法返回了一个Publisher,这样我们可以继续往下走,使用map操作符去做一些类型转换,这里在map操作符里面使用了之前定义的handleResponse方法。因为map方法闭包返回的参数和handleResponse接收的参数相同,所以可以简写,如下图:
请添加图片描述
另外在map操作符后还用了mapError操作符,将错误类型转换,否则就会报下面的错误:
在这里插入图片描述
主要原因是我们尝试将一个返回AnyPublisher<UIImage?, URLError>类型的表达式转换为返回AnyPublisher<UIImage?, Error>类型的表达式,但类型不匹配。
可以通过使用.mapError操作符来转换错误类型,将URLError转换为Error,以使类型匹配。

最后使用eraseToAnyPublisher()类型抹除到统一的AnyPublisher

下面在看看调用订阅的地方,在ViewModel中定义了如下方法:

func fetchImageWithCombine() {downloader.fetchImageWithCombine().sink { _ in} receiveValue: { [weak self] image inself?.image = image}.store(in: &cancellable)
}

通过sink添加订阅者,并处理收到的信息,最后别忘了store,否则出了方法作用域订阅就失效了。
在UI部分调用也是非常简单:

struct DownloadImageDemo: View {@StateObject private var viewModel = DownloadImageDemoViewModel()var body: some View {ZStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {
//            viewModel.fetchImageWithEscapingClosure()viewModel.fetchImageWithCombine()}}
}

async/await方式

async/await方式就用到了上一篇文章中说到的内容了。
首先还是处理网络层ImageDownloader,在其中添加方法,如下:

func fetchImageWithAsyncAndAwait() async throws -> UIImage? {do {let (data, response) = try await URLSession.shared.data(from: url)return handleResponse(data: data, response: response)} catch {throw error}
}

上面这个方法在方法名的后面添加了async,告诉系统这是个异步方法,另外还添加了throws,当错误的时候抛出异常。
在选择URLSession.shared的方法的时候我们看到有下面的这个方法,系统同样提供了一个异步的且抛出异常的data()方法。
请添加图片描述
所以我们也按照系统的规则去写。方法里面的do-catch等逻辑之前文章有介绍,这里就不多说了。
下面在ViewModel调用的方法里面,调用上面这个方法。

func fetchImageWithAsnynAndAwait() async {let image = try? await downloader.fetchImageWithAsyncAndAwait()await MainActor.run {self.image = image}
}

这个方法我们只是添加了async,并没有throws,这里我们暂时忽略异常错误,方法里面也用到了try?
调用async的异步方法,需要在前面加上await,并且刷新UI要回主线程哦。

最后就是在界面调用了:

struct DownloadImageDemo: View {@StateObject private var viewModel = DownloadImageDemoViewModel()var body: some View {ZStack {if let image = viewModel.image {Image(uiImage: image).resizable().scaledToFit().frame(width: 200, height: 200)}}.onAppear {
//            viewModel.fetchImageWithEscapingClosure()
//            viewModel.fetchImageWithCombine()Task {await viewModel.fetchImageWithAsnynAndAwait()}}}
}

因为调用异步方法需要在异步上下文环境中,所以我们将调用方法放到了Task闭包中。关于Task下一篇文章将重点介绍一下。

写在最后

本篇文章主要回顾了一下三种异步请求方式,@escaping closureCombineasync/await这三种方式,并做了一些代码示例,无论采用哪种方法,都是因人而异,因项目而异,不过还是希望大家跟上最新的步伐,让自己的代码更高效,更稳健,更易维护。

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

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • CAPL——定时器用法
  • Vue3:命名路由
  • 9-3 深度循环神经网络
  • 【微信小程序】全局数据共享 - MobX
  • 如何用Python调用智谱清言api进行智能问答
  • 【Java 设计模式】Business Delegate 模式:简化业务服务交互
  • 磷酸二氢钾溶液净化除杂,除重金属
  • 前端面试手撕题收集(自用)
  • 极狐GitLab 如何管理 Kubernetes 集群?
  • 监控电脑屏幕的软件叫什么?8款好用的监控电脑屏幕的软件推荐!
  • 程序员阿龙定制开发【精选】计算机毕业设计之:基于JAVA问卷调查系统
  • 数据结构(Java实现):链表与LinkedList
  • 无法验证 Anaconda 仓库证书
  • 【系统架构设计】软件架构设计(2)
  • 云计算实训33——高并发负载均衡项目(eleme)
  • httpie使用详解
  • JAVA SE 6 GC调优笔记
  • JAVA_NIO系列——Channel和Buffer详解
  • Java比较器对数组,集合排序
  • k8s如何管理Pod
  • Lsb图片隐写
  • Median of Two Sorted Arrays
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • php面试题 汇集2
  • Python_网络编程
  • Redis中的lru算法实现
  • Shadow DOM 内部构造及如何构建独立组件
  • vue从创建到完整的饿了么(18)购物车详细信息的展示与删除
  • 翻译:Hystrix - How To Use
  • 今年的LC3大会没了?
  • 浏览器缓存机制分析
  • 巧用 TypeScript (一)
  • 微信开放平台全网发布【失败】的几点排查方法
  • 移动端唤起键盘时取消position:fixed定位
  • 你对linux中grep命令知道多少?
  • 第二十章:异步和文件I/O.(二十三)
  • 基于django的视频点播网站开发-step3-注册登录功能 ...
  • #数据结构 笔记三
  • (160)时序收敛--->(10)时序收敛十
  • (C语言)逆序输出字符串
  • (二)PySpark3:SparkSQL编程
  • (剑指Offer)面试题41:和为s的连续正数序列
  • (七)MySQL是如何将LRU链表的使用性能优化到极致的?
  • (太强大了) - Linux 性能监控、测试、优化工具
  • (一)【Jmeter】JDK及Jmeter的安装部署及简单配置
  • (一)appium-desktop定位元素原理
  • (原創) 如何解决make kernel时『clock skew detected』的warning? (OS) (Linux)
  • (转) Android中ViewStub组件使用
  • (转)http协议
  • (转)scrum常见工具列表
  • (转)创业的注意事项
  • (转)可以带来幸福的一本书
  • .\OBJ\test1.axf: Error: L6230W: Ignoring --entry command. Cannot find argumen 'Reset_Handler'
  • .mat 文件的加载与创建 矩阵变图像? ∈ Matlab 使用笔记
  • .net dataexcel 脚本公式 函数源码