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

news2024/9/23 9:21:07

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

  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 in
            let image = self?.handleResponse(data: data, response: response)
            completion(image, error)
        }
        .resume()
    }
}

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

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

func fetchImageWithEscapingClosure() {
    downloader.fetchImageWithEscapingClosure { [weak self] image, _ in
        self?.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 in
            self?.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这三种方式,并做了一些代码示例,无论采用哪种方法,都是因人而异,因项目而异,不过还是希望大家跟上最新的步伐,让自己的代码更高效,更稳健,更易维护。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2081555.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基于微信小程序音乐分享与交流平台---附源码95587

摘 要 随着移动互联网的普及&#xff0c;微信小程序作为一种轻量级的应用程序&#xff0c;正逐渐成为人们获取信息和服务的便捷渠道。为了给用户提供便捷、多样化的音乐分享和交流渠道&#xff0c;本文提出了—种基于微信小程序的音乐分享与交流平台的设计与实现方案。通过该平…

SqlServer还原数据库后,数据库显示受限制用户解决方法

SqlServer还原数据库后&#xff0c;数据库显示受限制用户解决方法: 1.打开SSMS。 2.选中连接的数据库&#xff0c;右击鼠标右键&#xff0c;点击属性 3.在属性对话框中选择选项 4.在该对话框的右边&#xff0c;找到【限制访问】,并且将其改为&#xff1a;【MULTI_USER】 5.点击…

1个月2万粉的AI职业头像号,1分钟速成超详细教程

大家好 今天要拆解的是一个**小红书的AI职业头像号博主&#xff0c;**博主使用了手绘的二次元风格头像&#xff0c;不到1个月&#xff0c;现在的粉丝数已经1.9万了&#xff0c;收藏点赞9万。 有图有真相&#xff1a; 一、账号作品分析 这个博主的账号都是图文笔记。使用AI制…

【Google Maps JavaScript API】Simple Click Events 详解

文章目录 一、Simple Click Events 简介1. 什么是 Simple Click Events&#xff1f;2. 为什么使用 Simple Click Events&#xff1f; 二、Simple Click Events 的实现1. 基本代码结构2. 设置地图样式3. 初始化地图 三、处理点击事件1. 为标记添加点击事件2. 中心改变事件 四、完…

【JAVA基础】抽象类

抽象类 引言抽象类 抽象方法 引言 ​ 在面向对象的概念中&#xff0c;所有的对象都是通过类来描绘的&#xff0c;但是反过来&#xff0c;并不是所有的类都是用来描绘对象的&#xff0c;如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类。 …

【Google Maps JavaScript API】Geolocation功能实现用户位置定位

文章目录 一、什么是Geolocation&#xff1f;二、Geolocation的应用场景三、如何使用Geolocation功能1. 初始化地图2. 编写初始化地图的JavaScript代码3. 代码解析初始化地图创建定位按钮获取用户位置处理定位错误 4. 样式设置5. 运行示例 四、注意事项五、总结 Google Maps Ja…

【GIT】Idea中的git命令使用-全网最新详细(包括现象含义)

原文网址&#xff1a;【GIT】Idea中的git命令使用-全网最新详细&#xff08;包括现象含义&#xff09; 文章目录 **命令1&#xff1a;查看当前所处分支&#xff1a;****命令2&#xff1a;拉取最新代码&#xff1a;****命令3&#xff1a;切换分支&#xff1a;****命令4&#xff…

AIOps探索 | 运维应急的六个阶段

当下&#xff0c;金融科技快速发展的时代&#xff0c;银行和金融机构的IT系统日益复杂&#xff0c;业务量呈指数式增长。面对这一挑战&#xff0c;运维应急已成为确保金融服务稳定性和可靠性的关键因素。 智能运维应急即在IT系统出现异常或故障时&#xff0c;快速发现问题、准…

【达梦数据库】数据库频繁崩溃记录-非dump分析

这里写目录标题 背景查找原因问题解决补充 背景 用户反映系统CentOS上的数据库频繁崩溃&#xff0c;系统没有崩溃过&#xff0c;希望帮忙分析下 查找原因 查看数据库运行日志&#xff1a;无任何报错 查看数据库错误日志&#xff1a;无报错 查看OS运行日志&#xff0c;/var…

EPLAN中如何制作标题页和封页?

EPLAN中如何制作标题页和封页? 如下图所示,我们打开EPLAN,新建一个项目,

开学季有什么必买的好物?提升学习效率的好物来啦!学生党必看!

对于即将开学的学生们来说&#xff0c;选择一款性价比高的电容笔是非常重要的。它不仅能够提升学习效率&#xff0c;还能带来更加流畅舒适的书写与绘画体验。接下来&#xff0c;我将推荐一款非常适合学生使用的电容笔&#xff0c;它不仅性能可靠&#xff0c;而且价格亲民&#…

大语言模型向量检索技术综述:背景知识、数据效率、泛化能力、多任务学习、未来趋势

预训练语言模型如BERT和T5&#xff0c;是向量检索(后续文中使用密集检索)的关键后端编码器。然而&#xff0c;这些模型通常表现出有限的泛化能力&#xff0c;并在提高领域内准确性方面面临挑战。最近的研究探索了使用大型语言模型&#xff08;LLMs&#xff09;作为检索器&#…

苹果机器人计划:能否成为智能家居的破局者?

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

ADB 获取屏幕坐标,并模拟滑动和点击屏幕

本文声明:本文是参考https://blog.csdn.net/beyond702/article/details/69258932编制。同时,补充了在windows系统模式下,详细的获取屏幕坐标的步骤。 1.判断设备与windows电脑USB连接是否正常 在CMD窗口输入命令:ADB devices,按ENTER键,输出如下结果,则表示连接正常。 …

LLM大模型微调心得:全面经验总结与技巧分享

导读 模型越大对显卡的要求越高&#xff0c;目前主流对大模型进行微调方法有三种&#xff1a;Freeze方法、P-Tuning方法和Lora方法。本文总结了作者在ChatGLM-6B模型微调的经验&#xff0c;并汇总了目前各类开源项目&数据。 写在前面 大型语言模型横行&#xff0c;之前非…

Android经典实战之Kotlin的delay函数和Java中的Thread.sleep有什么不同?

本文首发于公众号“AntDream”&#xff0c;欢迎微信搜索“AntDream”或扫描文章底部二维码关注&#xff0c;和我一起每天进步一点点 Kotlin 中的 delay 函数和 Java 中的 Thread.sleep 都用于暂停代码执行&#xff0c;但是它们用于不同的场景&#xff0c;并且有不同的实现和影响…

饭馆扫码点餐小程序什么方法进行开发

扫码点餐模式出现的时间已经比较久&#xff0c;其主要作用便是节约客商时间&#xff0c;客户自己点餐&#xff0c;商家响应餐品跟进&#xff0c;降低服务员长时间沟通成本&#xff0c;当然客户饭馆消费也不能只依靠工具&#xff0c;还是需要与服务结合&#xff0c;打造有温度的…

企业网络安全“九九八十一难”,且看XDR的黑!神!话!

近期&#xff0c;游戏《黑神话悟空》的热度攀升&#xff0c;不仅汇聚了全球玩家的目光&#xff0c;也悄然成为黑客及网络不法分子的目标。 游戏预热阶段&#xff0c;其剧情视频意外泄露&#xff0c;迅速在网络上引发热烈讨论与业内关注。随后&#xff0c;有黑客组织公开宣称将…

Tongweb8074+7049m4 安装TongFlowControl(by lqw)

文章目录 介绍安裝包和説明Tongweb8074 安装TongFlowControlTongweb7049m4 安装TongFlowControl 介绍 TongFlowControl是面向分布式服务架构的流量监控工具&#xff0c;是TongWeb基于QPS/并发数和调用关系的流量 控制功能&#xff0c;可在TongFlowControl控制台进行实时监控和…

pdf2md·技术调研

解析技术调研&#xff1a; 技术项源代码官方说明优/劣势补充1 VikParuchuri/marker &#xff08;Star 14.9K&#xff09; https://github.com/VikParuchuri/marker?tabreadme-ov-file 优点&#xff1a; 耗时少精度高&#xff08;有ocr模型加持&#xff09;缺点&#xff1a;…