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

news2025/1/23 3:50:38

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 Combine

class 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 {  // 1
            print("Cancelling existing run")
            cancellable?.cancel()
            self.activityIndicator.stopAnimating()
        }
        print("resetting all the steps")
        self.resetAllSteps()  // 2
        // driving it by attaching it to .sink
        self.activityIndicator.startAnimating()  // 3
        print("attaching a new sink to start things going")
        self.cancellable = coordinatedPipeline?  // 4
            .print()
            .sink(receiveCompletion: { completion in
                print(".sink() received the completion: ", String(describing: completion))
                self.activityIndicator.stopAnimating()
            }, receiveValue: { value in
                print(".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 value
    func 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 updated
    func createFuturePublisher(button: UIButton) -> AnyPublisher<Bool, Error> {  // 5
        return Future<Bool, Error> { promise in
            self.randomAsyncAPI() { (result, err) in
                if 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 pipeline
            self.markStepDone(button: button)
            return true
        }
        .eraseToAnyPublisher()
    }

    /// highlights a button and changes the background color to green
    /// - Parameter button: reference to button being updated
    func markStepDone(button: UIButton) {
        button.backgroundColor = .systemGreen
        button.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 = .lightGray
            button?.isHighlighted = false
        }
        self.activityIndicator.stopAnimating()
    }

    // MARK: - view setup

    override 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> in
            let 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 in
                    return true
                }
                .eraseToAnyPublisher()
            }
        .flatMap { _ in
            return self.createFuturePublisher(button: self.step3_button)
        }
        .flatMap { _ in
            return 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

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

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

相关文章

论文阅读-面向公平性的分布式系统负载均衡机制

摘要 当一组自利的用户在分布式系统中共享多个资源时&#xff0c;我们面临资源分配问题&#xff0c;即所谓的负载均衡问题。特别地&#xff0c;负载均衡被定义为将负载分配到分布式系统的服务器上&#xff0c;以便最小化作业响应时间并提高服务器的利用率。在本文中&#xff0…

【客户端】聊聊卸载安装测试、新安装测试和覆盖安装测试(持续更新中)

程序安装一般会有&#xff1a;全新安装、卸载安装、覆盖安装这几种&#xff0c;那么安装渠道和方式就非常的多样化了。iOS可以商店安装、文件安装&#xff0c;安卓有商店安装、渠道安装、APK安装 等等。 一、不同安装方式 通常来说&#xff0c;大部分用户都会走到覆盖安装&…

微信小程序(四十二)wechat-http拦截器

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.wechat-http请求的封装 2.wechat-http请求的拦截器的用法演示 源码&#xff1a; utils/http.js import http from "wechat-http"//设置全局默认请求地址 http.baseURL "https://live-api.ith…

【ES】--Elasticsearch的分词器详解

目录 一、前言二、分词器原理1、常用分词器2、ik分词器模式3、指定索引的某个字段进行分词测试3.1、采用ts_match_analyzer进行分词3.2、采用standard_analyzer进行分词三、如何调整分词器1、已存在的索引调整分词器2、特别的词语不能被拆开一、前言 最近项目需求,针对客户提…

SSM+SpringBoot框架

单例bean是线程安全的吗 AOP Spring事务失效 第四种&#xff0c;在方法内部使用&#xff0c;需要用代理类调用此方法 bean生命周期 bean的循环依赖 SpringMVC执行流程 、 SpringBoot自动配置原理 Spring常见注解 MyBatis执行流程 MyBatis延迟加载 MyBatis缓存

算法刷题:复写零

复写零 .习题链接题目描述算法原理初始值步骤1步骤2我的答案: . 习题链接 复写零 题目描述 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素…

C++,stl,set/mutiset详解

目录 1.set容器的构造和赋值 2.set的大小和交换 3.set的插入和删除 4.set的查找和统计 5.set和mutiset区别 6.pair对组的创建 7.set排序 1.set的内置类型指定排序规则 2.set的自定义数据类型指定排序 1.set容器的构造和赋值 #include<bits/stdc.h> using name…

Ps:统计

Ps菜单&#xff1a;文件/脚本/统计 Scripts/Statistics 统计 Statistics脚本命令提供了一种高效的方法来处理和分析大量图像&#xff0c;使用户能够自动执行复杂的图像分析任务&#xff0c;并在多个图像间应用统计学方法。这个功能极大地扩展了 Photoshop 在科学研究、图像编辑…

论文介绍 VolumeDiffusion

论文介绍 VolumeDiffusion: Flexible Text-to-3D Generation with Efficient Volumetric Encoder 关注微信公众号: DeepGo 源码地址&#xff1a; https://github.com/tzco/VolumeDiffusion 论文地址&#xff1a; https://arxiv.org/abs/2312.11459 VolumeDiffusion模型是一个从…

分享85个jQuery特效,总有一款适合您

分享85个jQuery特效&#xff0c;总有一款适合您 85个jQuery特效下载链接&#xff1a;https://pan.baidu.com/s/1-n18tdXFuTyYBvGOU94lIQ?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;收集整理更不…

数制和码制

目录 几种常见的数制 数制 基数 位权 常见的四种数制 十进制数 二进制数 八进制数 十六进制数 不同进制数的相互转换 例如 例如 编码 二-十进制码 例如 格雷码 例如 原码、反码和补码 几种常见的数制 关键术语 数制&#xff1a;以一组固定的符号和统一的规则来表示数值…

###51单片机学习(1)-----单片机烧录软件的使用,以及如何建立一个工程项目

前言&#xff1a;感谢您的关注哦&#xff0c;我会持续更新编程相关知识&#xff0c;愿您在这里有所收获。如果有任何问题&#xff0c;欢迎沟通交流&#xff01;期待与您在学习编程的道路上共同进步。 一. 两个主要软件的介绍 1.KeiluVision5软件 Keil uVision5是一款集成开发…

Vue中 常用的修饰符有哪些

Vue是一款建立在JavaScript框架上的开源前端库&#xff0c;已经成为当今前端开发人员最喜爱的选择之一。它的简洁语法和强大的功能使得开发者可以轻松地构建交互性的网页应用程序。在Vue中&#xff0c;修饰符是一个重要的概念&#xff0c;它们可以帮助我们更好地控制和定制DOM元…

python多线程连接MySQL查数案例

该博文展示地是基本示例&#xff0c;实际使用时可能需要进行调整。例如&#xff0c;你可能需要添加错误处理来确保数据库连接问题不会导致脚本崩溃&#xff0c;或者你可能需要调整查询以匹配你的数据。 此外&#xff0c;你需要确保你的系统有足够的内存和处理能力来支持并行处理…

Mysql-数据库压力测试

安装软件 官方软件 安装插件提供了更多的监听器选项 数据库驱动 数据库测试 配置 这里以一个简单的案例进行&#xff0c;进行连接池为10,20,30的梯度压测&#xff1a; select * from tb_order_item where id 1410932957404114945;新建一个线程组 新增一个连接池配置 新建一…

用HTML5 + JavaScript绘制花、树

用HTML5 JavaScript绘制花、树 <canvas>是一个可以使用脚本 (通常为JavaScript) 来绘制图形的 HTML 元素。 <canvas> 标签/元素只是图形容器&#xff0c;必须使用脚本来绘制图形。 HTML5 canvas 图形标签基础https://blog.csdn.net/cnds123/article/details/112…

【动态规划】【数学】【C++算法】1449. 数位成本和为目标值的最大数字

作者推荐 【深度优先搜索】【树】【图论】2973. 树中每个节点放置的金币数目 本文涉及知识点 动态规划汇总 LeetCode1449. 数位成本和为目标值的最大数字 给你一个整数数组 cost 和一个整数 target 。请你返回满足如下规则可以得到的 最大 整数&#xff1a; 给当前结果添加…

前端面试题——二叉树遍历

前言 二叉树遍历在各种算法和数据结构问题中都有广泛的应用&#xff0c;如二叉搜索树、表达式的树形表示、堆的实现等。同时也是前端面试中的常客&#xff0c;掌握好二叉树遍历算法对于一名合格的前端工程师来说至关重要。 概念 二叉树遍历&#xff08;Binary Tree Traversa…

【c语言】字符串常见函数 上

&#x1f388;个人主页&#xff1a;甜美的江 &#x1f389;欢迎 &#x1f44d;点赞✍评论⭐收藏 &#x1f917;收录专栏&#xff1a;c语言 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共同学习、交流进步&a…

鸿蒙开发第3篇__大数据量的列表加载性能优化

列表 是最常用到的组件 一 ForEach 渲染控制语法————Foreach Foreach的作用 遍历数组项&#xff0c;并创建相同的布局组件块在组件加载时&#xff0c; 将数组内容数据全部创建对应的组件内容&#xff0c; 渲染到页面上 const swiperImage: Resource[] {$r("app.me…