Swift Combine — Scheduler(subscribe(on:)和receive(on:)的使用)

SwiftCombine 框架中,Scheduler 是一个重要的概念,用于控制任务的调度和执行。本文将详细介绍 Scheduler 的作用、常见的 Scheduler 类型以及如何使用 Scheduler 来管理任务的执行。

Scheduler 的定义

Scheduler 用于管理任务的调度和执行,可以控制任务何时执行、在哪个线程或队列上执行以及执行的顺序。

你可以使用调度器来尽可能快地执行代码,或者在将来的某个时间执行。不同的调度器实现会根据自己的需要使用不同的时间保持系统。调度器将此表示为 SchedulerTimeType。由于该类型符合 SchedulerTimeIntervalConvertible,因此您可以始终使用 .milliseconds(500) 等方便函数来表示这些时间。调度器可以接受选项来控制它们执行传递给它们的操作的方式。这些选项可能控制哪些线程或分发队列执行操作等因素。

常见的 Scheduler 类型

常见的 Scheduler 类型包括一下几种:

  • DispatchQueue: GCD(Grand Central Dispatch)中的调度器,用于在特定的调度队列上执行任务:串行、并发、主线程和全局队列。通常情况下,将后台任务分配到串行和全局队列上,而将与用户界面相关的任务分配到主线程队列上。
  • RunLoop:用于处理事件循环的机制,可以在特定的 RunLoop 上执行任务,如主线程的 RunLoop
  • OperationQueue:在特定的操作队列中执行任务。与DispatchQueue类似,使用OperationQueue.main进行UI操作,并使用其他队列进行后台操作。
  • ImmediateScheduler: 是一个立即执行任务的调度器,通常用于测试和调试目的。

使用RunLoop.main, DispatchQueue.mainOperationQueue.main都是和UI操作相关的,三者之间基本没什么区别。

默认Scheduler 类型

如果没有指定Scheduler,那么系统会默认给添加一个,而这个Scheduler的类型和Publisher发送数据时的类型一致,比如主线程发送的数据,那么接收也在主线程。

class SchedulerViewModel: ObservableObject {let publisher = PassthroughSubject<String, Never>()private var cancellable = Set<AnyCancellable>()init() {setUpSubscription()}func setUpSubscription() {publisher.sink { value inprint("\(value) is on main thread: \(Thread.isMainThread)")}.store(in: &cancellable)}func sendMessage() {// on main threadpublisher.send("Text 1")// on other threadDispatchQueue.global().async { [weak self] inself?.publisher.send("Text 2")}}
}

上面的代码中,在sendMessage()方法中直接调用了两次send方法,第一个在主线程中调用,第二个在异步线程调用。sink的闭包中输出结果为:

Text 1 is on main thread: true
Text 2 is on main thread: false

Scheduler的使用

Combine 框架提供了两个基本的操作符来使用调度器:

  • subscribe(on:)subscribe(on:options:) 在指定的 Scheduler 上创建订阅(开始工作)。
  • receive(on:)receive(on:options:) 在指定的 Scheduler 上传递值。

subscribe(on:)

subscribe(on:) 会将当前订阅设置在你希望管理的调度器上。该操作符用于创建订阅、取消订阅和请求输入时使用的调度器。

从创建订阅到最后订阅者接收到数据的整个过程可以认为时一个管道,数据从上游流向下游。subscribe(on:) 会将调度器设置为订阅管道上游所使用的调度器。同时有个副作用是,subscribe(on:) 还会更改其下游调度器。

这里面需要说明一下什么时管道上游和管道下游,在stackoverflow中有人这么解释:

上游:

  • The actual performance of the subscription (receive subscription)
  • Requests from a subscriber to the upstream publisher asking for a new value
  • Cancel messages (these percolate upwards from the final subscriber)

上游大概指的就是在创建订阅,建立连接,请求数据和取消订阅的过程。

下游:

  • Values
  • Completions, consisting of either a failure (error) or completion-in-good-order (reporting that the publisher emitted its last value)

下游大概指的是Publisher发送数据一直到订阅者收到数据的过程。
当我们创建了Publisher,然后添加operator,最后sink或者assign的整个过程都应该算是下游。

subscribe(on:)大多情况下影响的是管道上游,比如我们自定义一个Publisher,起名MyPublisher

extension Publishers {struct MyPublisher: Publisher {typealias Output = Inttypealias Failure = Neverfunc receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Int == S.Input {debugPrint("receive: \(Thread.isMainThread)")subscriber.receive(subscription: Subscriptions.empty)_ = subscriber.receive(666)}}
}

MyPublisher中,定义了输出类型和失败类型,另外还必须要实现receive(subscriber:)方法。
receive(subscriber:)方法用于接受订阅者,并创建一个订阅对象,然后将该订阅对象提供给订阅者,以建立发布者和订阅者之间的连接。那么该方法内做的事情都属于管道上游的事情。我们在方法中加上一个打印。并且直接想订阅者发送了一个数据666.

Publishers.MyPublisher().map { _ inprint("Map: \(Thread.isMainThread)")}.sink { _ inprint("Sink: \(Thread.isMainThread)")}.store(in: &cancellable)

当调用上面代码的时候,打印输出:

"receive: true"
Map: true
Sink: true

因为没有调用subscribe(on:)设置,所有默认都是在主线程执行的。

下面加上subscribe(on:)方法,并设置为异步线程。

Publishers.MyPublisher().map { _ inprint("Map: \(Thread.isMainThread)")}.subscribe(on: DispatchQueue.global()) // 设置为异步线程。.sink { _ inprint("Sink: \(Thread.isMainThread)")}.store(in: &cancellable)

执行后打印结果为:

"receive: false"
Map: false
Sink: false

看结果都是在异步线程执行的,.subscribe(on: DispatchQueue.global())这段代码即是放在map操作符前面结果也是一样的。
subscribe(on:)就像上面说的不仅能影响管道上游的执行线程,也能影响管道下游的执行线程。

另外说一点,像是JustSequence这类的Publisher的管道上游就是在主线程执行的,而像URLSession.DataTaskPublisher就是在异步线程执行的。

如果sink闭包中要处理UI相关的东西,那就不能让subscribe(on:)影响到,除了在sink方法中手动切换到主线程,还有就是要用到receive(on:)方法了。

receive(on:)

针对Publisher使用subscribe(on:)并不多见,除非你的需求复杂一点,更多的时候我们使用receive(on:)方法,它允许你指定应该使用哪个调度程序向订阅者传递值。

说的直白一些就是在整个Publisher链中,在receive(on:)方法后面执行的操作都在receive(on:)方法决定的线程中执行。

比如刚才的代码中,在subscribe(on:)方法后面加上了receive(on:)方法。

Publishers.MyPublisher().map { _ inprint("Map: \(Thread.isMainThread)")}.subscribe(on: DispatchQueue.global()).receive(on: DispatchQueue.main).sink { _ inprint("Sink: \(Thread.isMainThread)")}.store(in: &cancellable)

执行打印结果:

"receive: false"
Map: false
Sink: true

可以看到sink方法实在主线程中调用的。
在整个Publisher链中,如果使用了subscribe(on:)方法,那么最好在后面也要使用receive(on:)方法。

我们知道URLSession.DataTaskPublisher时异步执行的,数据返回时通过一系列的转型操作等,最终还是要走到sink方法里,那么就可以在sink之前加上receive(on:)方法,并传入主线程参数。

下面在看一个只有receive(on:)方法的示例。

Publishers.MyPublisher().map { _ inprint("Map: \(Thread.isMainThread)")}.receive(on: DispatchQueue.global()).sink { _ inprint("Sink: \(Thread.isMainThread)")}.store(in: &cancellable)

在map后加上了receive(on:)方法,并传入异步线程参数。打印结果如下:

"receive: true"
Map: true
Sink: false

receive(on:)方法方法之前都是在主线程执行,receive(on:)方法之后就是异步线程执行了。

如果加一个receive(on:)方法不过瘾,再看看加两个的。

Publishers.MyPublisher().receive(on: DispatchQueue.global()) // 1 异步线程.map { _ inprint("Map: \(Thread.isMainThread)")}.receive(on: DispatchQueue.main) // 2 主线程.sink { _ inprint("Sink: \(Thread.isMainThread)")}.store(in: &cancellable)

先看打印结果:

"receive: true"
Map: false
Sink: true

管道上游肯定是在主线程了,然后调用了.receive(on: DispatchQueue.global()),之后的map操作就是异步线程执行了,然后又调用了.receive(on: DispatchQueue.main),回到了主线程,所以最后的sink就在主线程执行了。

在开发过程中使用receive(on:)方法的频率会多余subscribe(on:)方法。

写在最后

SchedulerCombine 中扮演着重要的角色,用于控制任务的调度和执行。通过指定不同的 Scheduler,可以控制任务在不同的线程或队列上执行,确保任务按照预期顺序执行。使用 Scheduler 可以帮助我们更好地管理任务的执行,提高代码的可读性和性能。

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

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

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

相关文章

Cell2Sentence:为LLM传输生物语言

像GPT这样的LLM在自然语言任务上表现出了令人印象深刻的性能。这里介绍一种新的方法&#xff0c;通过将基因表达数据表示为文本&#xff0c;让这些预训练的模型直接适应生物背景&#xff0c;特别是单细胞转录组学。具体来说&#xff0c;Cell2Sentence将每个细胞的基因表达谱转换…

AI学习指南机器学习篇-朴素贝叶斯模型应用与Python实践

AI学习指南机器学习篇-朴素贝叶斯模型应用与Python实践 在本篇博客中&#xff0c;我们将会介绍如何使用Python中的Scikit-learn库来实现朴素贝叶斯模型。朴素贝叶斯是一种常见的机器学习算法&#xff0c;它在文本分类、垃圾邮件检测等领域有着广泛的应用。通过本文的学习&…

小学数学蝴蝶模型详解

蝴蝶模型 1.蝴蝶模型仅存在于梯形中&#xff0c;是连接梯形两条对角线而形成的&#xff0c;如下图&#xff1a; 2.蝴蝶模型有几条公式 (1) (2) S△AODS△BOC 等等......

commons-pool2 对象池技术

对象池&#xff1f; 让任意对象实现池功能&#xff0c;只要结合使用两个类GenericObjectPool 和PooledObjectFactory &#xff0c;这个池子可以实现: &#xff08;1&#xff09;minIdle个数保证&#xff1a;通过配置&#xff0c;测试并清除池子中的空闲对象&#xff0c;以保证…

多商户零售外卖超市外卖商品系统源码

构建你的数字化零售王国 一、引言&#xff1a;数字化零售的崛起 在数字化浪潮的推动下&#xff0c;零售业务正经历着前所未有的变革。多商户零售外卖超市商品系统源码应运而生&#xff0c;为商户们提供了一个全新的数字化零售解决方案。通过该系统源码&#xff0c;商户们可以…

BFS:解决拓扑排序问题

文章目录 什么是拓扑排序&#xff1f;关于拓扑排序的题1.课程表2.课程表Ⅱ3.火星词典 总结 什么是拓扑排序&#xff1f; 要知道什么拓扑排序我们首先要知道什么是有向无环图&#xff0c;有向无环图我们看名字其实就很容易理解&#xff0c;有向就是有方向&#xff0c;无环就是没…

C# 热插拔---插件开发

热插拔是以多态&#xff0c;文件监控&#xff0c;反射为基础的。所以用到的是FileSystemWatcher类和 Assembly 类&#xff0c;主要原理就是动态加载dll文件&#xff0c;而要监控dll文件&#xff0c;最好的就是用FileSystemWatcher类&#xff0c;它可以实时监控指定路径下的文件…

028基于SSM+Jsp的电影售票系统

开发语言&#xff1a;Java框架&#xff1a;ssm技术&#xff1a;JSPJDK版本&#xff1a;JDK1.8服务器&#xff1a;tomcat7数据库&#xff1a;mysql 5.7&#xff08;一定要5.7版本&#xff09;数据库工具&#xff1a;Navicat11开发软件&#xff1a;eclipse/myeclipse/ideaMaven包…

梅雨季要祛湿!分不清寒湿和湿热,小心越祛越湿!4个方法,助你温和排湿热与寒湿

梅雨季又又又又到了&#xff0c;苏州的雨已经连下3天了&#xff0c;到处都湿哒哒、黏糊糊&#xff01;胃口不好、身体酸重、心情不好……湿气太重了&#xff01; 中医有一句话说“湿气在&#xff0c;百病害&#xff0c;湿气除&#xff0c;百病无”&#xff0c;意思是“湿”为万…

Java应用中的数据加密与解密技术详解

在当今的网络环境中&#xff0c;数据安全变得尤为重要。无论是保护用户隐私还是确保业务数据不被篡改&#xff0c;加密技术都是不可或缺的一环。Java提供了丰富的API来支持各种加密算法&#xff0c;包括对称加密、非对称加密以及消息摘要等。本文将详细介绍如何在Java应用中使用…

编写一个可复用且使用方式简单的部署脚本

只需一行命令就可使用应用部署或重新部署 当我们部署Java项目时&#xff0c;一般有两种部署方式&#xff1a; 使用java -jar命令来运行jar包将应用打成jar包以容器的方式进行部署 本篇文章主要讲解第二种方式&#xff0c;以部署xxl-job-admin为例 1.编写restart.sh脚本&…

IDEA启动项目Error:java: JDK isn‘t specified for module ‘test‘

错误原因&#xff1a; idea自带JDK不匹配导致项目启动失败 解决方法&#xff1a; 修改idea自带JDK为自己安装的JDK 调整步骤&#xff1a;

rk3568 Android12 屏幕显示方向

rk3568 Android12 屏幕显示方向 在Android设备中,方向传感器的信息通常由加速度计和磁力计共同提供。开启自动旋转屏幕时,将设备从纵向转为横向或从横向转为纵向时,屏幕的内容会自动根据设备的方向进行调整。如果不希望屏幕自动旋转,可以禁用该选项并屏幕方向转为默认方向…

《编译原理》阅读笔记:p18

《编译原理》学习第 3 天&#xff0c;p18总结&#xff0c;总计 14页。 一、技术总结 1.assembler (1)计算机结构 要想学习汇编的时候更好的理解&#xff0c;要先了解计算机的结构&#xff0c;以下是本人学习汇编时总结的一张图&#xff0c;每当学习汇编时&#xff0c;看到“…

线上OOM问题排查总结

自己搭建了一个小博客&#xff0c;该文章与博客文章同步。 一般情况下&#xff0c;出现OOM主要有一下三种原因。 一次性申请对象的太多。更改申请对象数量。内存资源耗尽未释放。找到未释放的对象进行释放。本身资源不够。jmap -heap 查看堆信息。 分几种情况解决&#xff1…

多模态-大模型:MLLM综述(适用初学)

文章目录 前言一、多模态模型基础知识二、多模态指令调优&#xff08;M-IT&#xff09;1.MLLM基础2.模态对齐3.数据获取4.模态桥接 三、多模态上下文学习&#xff08;M-ICL&#xff09;三、多模态思维链 (M-CoT)四、LLM辅助视觉推理1.训练范式2. LLM功能 五、一些思考总结 前言…

网络通信基础-02

什么是ARP协议 ARP&#xff08;Address Resolution Protocol&#xff0c;地址解析协议&#xff09;是一种网络协议&#xff0c;用于将网络层的IP地址解析为物理层的MAC地址。在计算机网络中&#xff0c;通信的两个设备之间需要知道对方的MAC地址才能进行数据传输&#xff0c;而…

OS中断机制-外部中断触发

中断函数都定义在中断向量表中,外部中断通过中断跳转指令触发中断向量表中的中断服务函数,中断指令可以理解为由某个中断寄存器的状态切换触发的汇编指令,这个汇编指令就是中断跳转指令外部中断通过在初始化的时候使能对应的中断服务函数如何判断外部中断被触发的条件根据Da…

关于ONLYOFFICE8.1版本桌面编辑器测评——AI时代的领跑者

关于作者&#xff1a;个人主页 目录 一.产品介绍 1.关于ONLYOFFICE 2.关于产品的多元化功能 二.关于产品体验方式 1.关于套件的使用网页版登录 2.关于ONLYOFFICE本地版 三.关于产品界面设计 四.关于产品文字处理器&#xff08;Document Editor&#xff09; 1.电子表格&a…

昇思25天学习打卡营第6天 | 函数式自动微分

神经网络的训练主要使用反向传播算法&#xff0c; 模型预测值&#xff08;logits&#xff09;与正确标签&#xff08;label&#xff09;送入损失函数&#xff08;loss function&#xff09;获得loss&#xff0c; 然后进行反向传播计算&#xff0c;求得梯度&#xff08;gradie…