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

Swift Combine 有序的异步操作 从入门到精通十二

Combine 系列

  1. Swift Combine 从入门到精通一
  2. Swift Combine 发布者订阅者操作者 从入门到精通二
  3. Swift Combine 管道 从入门到精通三
  4. Swift Combine 发布者publisher的生命周期 从入门到精通四
  5. Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五
  6. Swift Combine 订阅者Subscriber的生命周期 从入门到精通六
  7. Swift 使用 Combine 进行开发 从入门到精通七
  8. Swift 使用 Combine 管道和线程进行开发 从入门到精通八
  9. Swift Combine 使用 sink, assign 创建一个订阅者 从入门到精通九
  10. Swift Combine 使用 dataTaskPublisher 发起网络请求 从入门到精通十
  11. Swift Combine 用 Future 来封装异步请求 从入门到精通十一
    在这里插入图片描述

目的:使用 Combine 的管道来显式地对异步操作进行排序

这类似于一个叫做 “promise chaining” 的概念。 虽然你可以将 Combine 处理的和其行为一致,但它可能不能良好地替代对 promise 库的使用。 主要区别在于,promise 库总是将每个 promise 作为单一结果处理,而 Combine 带来了可能需要处理许多值的复杂性。

任何需要按特定顺序执行的异步(或同步)任务组都可以使用 Combine 管道进行协调管理。 通过使用 Future 操作符,可以捕获完成异步请求的行为,序列操作符提供了这种协调功能的结构。

通过将任何异步 API 请求与 Future 发布者进行封装,然后将其与 flatMap 操作符链接在一起,你可以以特定顺序调用被封装的异步 API 请求。 通过使用 Future 或其他发布者创建多个管道,使用 zip 操作符将它们合并之后等待管道完成,通过这种方法可以创建多个并行的异步请求。

如果你想强制一个 Future 发布者直到另一个发布者完成之后才被调用,你可以把 future 发布者创建在 flatMap 的闭包中,这样它就会等待有值被传入 flatMap 操作符之后才会被创建。

通过组合这些技术,可以创建任何并行或串行任务的结构。

如果后面的任务需要较早任务的数据,这种协调异步请求的技术会特别有效。 在这些情况下,所需的数据结果可以直接通过管道传输。

此排序的示例如下。 在此示例中,按钮在完成时会高亮显示,按钮的排列顺序是特意用来显示操作顺序的。 整个序列由单独的按钮操作触发,该操作还会重置所有按钮的状态,如果序列中有尚未完成的任务,则都将被取消。 在此示例中,异步 API 请求会在随机的时间之后完成,作为例子来展示时序的工作原理。

创建的工作流分步表示如下:

  • 步骤 1 先运行。
  • 步骤 2 有三个并行的任务,在步骤 1 完成之后运行。
  • 步骤 3 等步骤 2 的三个任务全部完成之后,再开始执行。
  • 步骤 4 在步骤 3 完成之后开始执行。

此外,还有一个 activity indicator 被触发,以便在序列开始时开始动画,在第 4 步完成时停止。

UIKit-Combine/AsyncCoordinatorViewController.swift

import UIKit
import Combineclass AsyncCoordinatorViewController: UIViewController {@IBOutlet weak var startButton: UIButton!@IBOutlet weak var step1_button: UIButton!@IBOutlet weak var step2_1_button: UIButton!@IBOutlet weak var step2_2_button: UIButton!@IBOutlet weak var step2_3_button: UIButton!@IBOutlet weak var step3_button: UIButton!@IBOutlet weak var step4_button: UIButton!@IBOutlet weak var activityIndicator: UIActivityIndicatorView!var cancellable: AnyCancellable?var coordinatedPipeline: AnyPublisher<Bool, Error>?@IBAction func doit(_ sender: Any) {runItAll()}func runItAll() {if self.cancellable != nil {  // 1print("Cancelling existing run")cancellable?.cancel()self.activityIndicator.stopAnimating()}print("resetting all the steps")self.resetAllSteps()  // 2// driving it by attaching it to .sinkself.activityIndicator.startAnimating()  // 3print("attaching a new sink to start things going")self.cancellable = coordinatedPipeline?  // 4.print().sink(receiveCompletion: { completion inprint(".sink() received the completion: ", String(describing: completion))self.activityIndicator.stopAnimating()}, receiveValue: { value inprint(".sink() received value: ", value)})}// MARK: - helper pieces that would normally be in other files// this emulates an async API call with a completion callback// it does nothing other than wait and ultimately return with a boolean valuefunc randomAsyncAPI(completion completionBlock: @escaping ((Bool, Error?) -> Void)) {DispatchQueue.global(qos: .background).async {sleep(.random(in: 1...4))completionBlock(true, nil)}}/// Creates and returns pipeline that uses a Future to wrap randomAsyncAPI/// and then updates a UIButton to represent the completion of the async/// work before returning a boolean True./// - Parameter button: button to be updatedfunc createFuturePublisher(button: UIButton) -> AnyPublisher<Bool, Error> {  // 5return Future<Bool, Error> { promise inself.randomAsyncAPI() { (result, err) inif let err = err {promise(.failure(err))} else {promise(.success(result))}}}.receive(on: RunLoop.main)// so that we can update UI elements to show the "completion"// of this step.map { inValue -> Bool in  // 6// intentionally side effecting here to show progress of pipelineself.markStepDone(button: button)return true}.eraseToAnyPublisher()}/// highlights a button and changes the background color to green/// - Parameter button: reference to button being updatedfunc markStepDone(button: UIButton) {button.backgroundColor = .systemGreenbutton.isHighlighted = true}func resetAllSteps() {for button in [self.step1_button, self.step2_1_button, self.step2_2_button, self.step2_3_button, self.step3_button, self.step4_button] {button?.backgroundColor = .lightGraybutton?.isHighlighted = false}self.activityIndicator.stopAnimating()}// MARK: - view setupoverride func viewDidLoad() {super.viewDidLoad()self.activityIndicator.stopAnimating()// Do any additional setup after loading the view.coordinatedPipeline = createFuturePublisher(button: self.step1_button)  // 7.flatMap { flatMapInValue -> AnyPublisher<Bool, Error> inlet step2_1 = self.createFuturePublisher(button: self.step2_1_button)let step2_2 = self.createFuturePublisher(button: self.step2_2_button)let step2_3 = self.createFuturePublisher(button: self.step2_3_button)return Publishers.Zip3(step2_1, step2_2, step2_3).map { _ -> Bool inreturn true}.eraseToAnyPublisher()}.flatMap { _ inreturn self.createFuturePublisher(button: self.step3_button)}.flatMap { _ inreturn self.createFuturePublisher(button: self.step4_button)}.eraseToAnyPublisher()}
}
  1. runItAll 协调此工作流的进行,它从检查当前是否正在执行开始。 如果是,它会在当前的订阅者上调用 cancel()
  2. resetAllSteps 通过遍历所有表示当前工作流状态的按钮,并将它们重置为灰色和未高亮以回到初始状态。 它还验证 activity indicator 当前未处于动画中。
  3. 然后我们开始执行请求,首先开启 activity indicator 的旋转动画。
  4. 使用 sink 创建订阅者并存储对工作流的引用。 被订阅的发布者是在该函数外创建的,允许被多次复用。 管道中的 print 操作符用于调试,在触发管道时在控制台显示输出。
  5. 每个步骤都由 Future 发布者紧跟管道构建而成,然后立即由管道操作符切换到主线程,然后更新 UIButton 的背景色,以显示该步骤已完成。 这封装在 createFuturePublisher 的调用中,使用 eraseToAnyPublisher 以简化返回的类型。
  6. map 操作符用于创建并更新 UIButton,作为特定的效果以显示步骤已完成。
  7. 创建整个管道及其串行和并行任务结构,是结合了对 createFuturePublisher 的调用以及对 flatMapzip 操作符的使用共同完成的。

另请参阅

  • 通过包装基于 delegate 的 API 创建重复发布者
  • 使用此代码的 ViewController 在 github 的项目中 UIKit-Combine/AsyncCoordinatorViewController.swift.

参考

https://heckj.github.io/swiftui-notes/index_zh-CN.html

代码

https://github.com/heckj/swiftui-notes

相关文章:

Swift Combine 有序的异步操作 从入门到精通十二

Combine 系列 Swift Combine 从入门到精通一Swift Combine 发布者订阅者操作者 从入门到精通二Swift Combine 管道 从入门到精通三Swift Combine 发布者publisher的生命周期 从入门到精通四Swift Combine 操作符operations和Subjects发布者的生命周期 从入门到精通五Swift Com…...

国产航顺HK32F030M: 超声波测距模块串口通信数据接收与处理

参考代码 /************************************************************************************************** * file usart_async_tx_no_int_rx_rxneint.c * brief 异步串口通信例程, 通过查询TXE标志发送数据,通过RXNE中断接收数据,当中断接收到数据后会将 * …...

idea:如何连接数据库

1、在idea中打开database: 2、点击 ‘’ ---> Data Source ---> MySQL 3、输入自己的账号和密码其他空白处可以不填&#xff0c;用户和密码可以在自己的mysql数据库中查看 4、最后选择自己需要用的数据库&#xff0c;点击运用ok&#xff0c;等待刷新即可 最后&#xff1a…...

JS中ES5和ES6的区别

前言 ES5是JavaScript的第五个修订版本&#xff0c;于2009年发布。而ES6是JavaScript的第六个修订版本&#xff0c;也称为ES2015&#xff0c;于2015年发布。以下是它们两个版本之前的一些区别&#xff1a; 变量声明方式 在ES5中&#xff0c;使用var关键字进行变量声明&#…...

软考24-上午题-图1

一、数据结构的回忆 线性结构&#xff1a;&#xff08;一对一&#xff09; 除首结点没有前驱、末尾结点没有后继外&#xff0c;一个结点只有唯一的一个直接前驱和唯一的一个直接后继。 树结构&#xff1a;&#xff08;一对多&#xff09; 除根节点没有前驱节点外&#xff0c;…...

书生·浦语大模型第四课作业

基础作业&#xff1a; 构建数据集&#xff0c;使用 XTuner 微调 InternLM-Chat-7B 模型, 让模型学习到它是你的智能小助手&#xff0c;效果如下图所示&#xff0c;本作业训练出来的模型的输出需要将不要葱姜蒜大佬替换成自己名字或昵称&#xff01; 1.安装 # 如果你是在 Int…...

勒索攻击风起云涌,Sodinokibi深度分析

前言 Sodinokibi勒索病毒&#xff0c;又称为REvil勒索病毒&#xff0c;这款勒索病毒最早在国内被发现是2019年4月份&#xff0c;笔者在早期分析这款勒索病毒的时候就发现它与其他勒索病毒不同&#xff0c;于是被笔者称为GandCrab勒索病毒的“接班人”&#xff0c;为什么它是Ga…...

1124. 骑马修栅栏(欧拉路径,模板)

农民John每年有很多栅栏要修理。 他总是骑着马穿过每一个栅栏并修复它破损的地方。 John是一个与其他农民一样懒的人。 他讨厌骑马&#xff0c;因此从来不两次经过一个栅栏。 你必须编一个程序&#xff0c;读入栅栏网络的描述&#xff0c;并计算出一条修栅栏的路径&#xf…...

C# CAD2016获取数据操作BlockTableRecord、Polyline、DBObject

一、数据操作说明 //DBObject 基础类 DBObject dbObj (DBObject)tr.GetObject(outerId, OpenMode.ForRead); //Polyline 线段类 Polyline outerPolyline (Polyline)tr.GetObject(outerId, OpenMode.ForRead); //BlockTableRecord 块表类 BlockTableRecord modelSpace (Bloc…...

java SSM新闻管理系统myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM新闻管理系统是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S…...

Linux_线程

线程与进程 多级页表 线程控制 线程互斥 线程同步 生产者消费者模型 常见概念 下面选取32位系统举例。 一.线程与进程 上图是曾经我们认为进程所占用的资源的集合。 1.1 线程概念 线程是一个执行分支&#xff0c;执行粒度比进程细&#xff0c;调度成本比进程低线程是cpu…...

【selenium】

selenium是一个Web的自动化测试工具&#xff0c;最初是为网站自动化测试而开发的。Selenium可以直接调用浏览器&#xff0c;它支持所有主流的浏览器。其本质是通过驱动浏览器&#xff0c;完成模拟浏览器操作&#xff0c;比如挑战&#xff0c;输入&#xff0c;点击等。 下载与打…...

HX711压力传感器学习一(STM32)

目录 原理图&#xff1a;​ 引脚介绍&#xff1a; HX711介绍工作原理: 程序讲解&#xff1a; 整套工程&#xff1a; 发送的代码工程&#xff0c;与博客的不一致&#xff0c;如果编译有报错请按照报错和博客进行修改 原理图&#xff1a; 引脚介绍&#xff1a; VCC和GND引…...

作业2.13

1、选择题 1.1、若有定义语句&#xff1a;int a[3][6]; &#xff0c;按在内存中的存放顺序&#xff0c;a 数组的第10个元素是 D A&#xff09;a[0][4] B) a[1][3] C)a[0][3] D)a[1][4] 1.2、有数组 int a[5] {10&#xff0c;20&#xff0c;30&#xff0c;40&#xff0c;50},…...

ArcGIS学习(七)图片数据矢量化

ArcGIS学习(七)图片数据矢量化 通过上面几个任务的学习,大家应该已经掌握了ArcGIS的基础操作,并且学习了坐标系和地理数据库这两个非常重要且稍微难一些的专题。从这一任务开始,让我们进入到实战案例板块。 首先进入第一个案例一一图片数据矢量化。 我们在平时的工作学…...

G口大流量服务器选择的关键点有哪些?

G口服务器指的是接入互联网的带宽达到1Gbps以上的服务器&#xff0c;那么选择使用G口大流量服务器的用户需要注意哪些选择 关键点呢?小编为您整理关于G口大流量服务器的关键点。 G口服务器通常被用于需要大带宽支持的业务场景&#xff0c;比如视频流媒体、金融交易平台、电子商…...

MongoDB聚合:$unset

使用$unset阶段可移除文档中的某些字段。从版本4.2开始支持。 语法 移除单个字段&#xff0c;可以直接指定要移除的字段名&#xff1a; { $unset: "<field>" }移除多个字段&#xff0c;可以指定一个要移除字段名的数组&#xff1a; { $unset: [ "<…...

DS Wannabe之5-AM Project: DS 30day int prep day14

Q1. What is Alexnet? Q2. What is VGGNet? Q3. What is VGG16? Q4. What is ResNet? At the ILSVRC 2015, so-called Residual Neural Network (ResNet) by the Kaiming He et al introduced the anovel architecture with “skip connections” and features heavy b…...

【程序设计竞赛】C++与Java的细节优化

必须强调下&#xff0c;以下的任意一种优化&#xff0c;都应该是在本身采用的算法没有任何问题情况下的“锦上添花”&#xff0c;而不是“雪中送炭”。 如果下面的说法存在误导&#xff0c;请专业大佬评论指正 读写优化 C读写优化——解除流绑定 在ACM里&#xff0c;经常出现…...

Java缓冲流——效率提升深度解析

前言 大家好&#xff0c;我是chowley&#xff0c;在我之前的项目中&#xff0c;用到了缓冲流来提高字符流之间的比较速度&#xff0c;缓冲流的主要作用类似于数据库缓存&#xff0c;提高IO操作效率。 缓冲流 在Java的输入输出操作中&#xff0c;缓冲流是提高性能的重要工具之…...

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…...

【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15

缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下&#xff1a; struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

MFC内存泄露

1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...

Nginx server_name 配置说明

Nginx 是一个高性能的反向代理和负载均衡服务器&#xff0c;其核心配置之一是 server 块中的 server_name 指令。server_name 决定了 Nginx 如何根据客户端请求的 Host 头匹配对应的虚拟主机&#xff08;Virtual Host&#xff09;。 1. 简介 Nginx 使用 server_name 指令来确定…...

在Ubuntu中设置开机自动运行(sudo)指令的指南

在Ubuntu系统中&#xff0c;有时需要在系统启动时自动执行某些命令&#xff0c;特别是需要 sudo权限的指令。为了实现这一功能&#xff0c;可以使用多种方法&#xff0c;包括编写Systemd服务、配置 rc.local文件或使用 cron任务计划。本文将详细介绍这些方法&#xff0c;并提供…...

dify打造数据可视化图表

一、概述 在日常工作和学习中&#xff0c;我们经常需要和数据打交道。无论是分析报告、项目展示&#xff0c;还是简单的数据洞察&#xff0c;一个清晰直观的图表&#xff0c;往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server&#xff0c;由蚂蚁集团 AntV 团队…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合

在汽车智能化的汹涌浪潮中&#xff0c;车辆不再仅仅是传统的交通工具&#xff0c;而是逐步演变为高度智能的移动终端。这一转变的核心支撑&#xff0c;来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒&#xff08;T-Box&#xff09;方案&#xff1a;NXP S32K146 与…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...

uniapp 开发ios, xcode 提交app store connect 和 testflight内测

uniapp 中配置 配置manifest 文档&#xff1a;manifest.json 应用配置 | uni-app官网 hbuilderx中本地打包 下载IOS最新SDK 开发环境 | uni小程序SDK hbulderx 版本号&#xff1a;4.66 对应的sdk版本 4.66 两者必须一致 本地打包的资源导入到SDK 导入资源 | uni小程序SDK …...