Swift Combine 学习(二):发布者 Publisher

  • Swift Combine 学习(一):Combine 初印象
  • Swift Combine 学习(二):发布者 Publisher
  • Swift Combine 学习(三):Subscription和 Subscriber
  • Swift Combine 学习(四):操作符 Operator
  • Swift Combine 学习(五):Backpressure和 Scheduler
  • Swift Combine 学习(六):自定义 Publisher 和 Subscriber
  • Swift Combine 学习(七):实践应用场景举例

    文章目录

      • 引言
      • 发布者 (`Publisher`)
        • `ConnectablePublisher`
        • 引用共享
        • Subject
      • 结语

引言

在上一篇文章中,初步简单的介绍了 Combine 框架的基本概念,大概有了一个初印象。本文将开始介绍 Combine 中的发布者(Publisher)。Publisher 是 Combine 框架的核心组件之一,负责生成和传递数据流。通过理解 Publisher 的类型、特性和使用方法,可以更好地在 Combine 中生成和管理数据流。

发布者 (Publisher)

Declares that a type can transmit a sequence of values over time.

声明一个类型可以随着时间推移传输一系列的值。

发布者(Publisher)是 Combine 框架中的核心概念之一,它定义如何生成并传递一系列值。像是观察者模式中的 Observable。它可以使用 operator 来组合变换,生成新的 Publisher。发布者会随时间的推移将一系列值发送给一个或多个订阅者 Subscriber。发布者遵循 Publisher 协议,该协议定义了两个关联类型:

  1. Output:发布者发送出的值。
  2. Failure:发布者可能产生的错误,遵循 Error 协议。
public protocol Publisher<Output, Failure> {/// The kind of values published by this publisher./// 此发布者发布的值的类型associatedtype Output/// The kind of errors this publisher might publish./// 此发布者可能发布的错误类型/// Use `Never` if this `Publisher` does not publish errors./// 如果这个发布者不会发错误,用 Neverassociatedtype Failure : Error/// Attaches the specified subscriber to this publisher./// 将指定的订阅者附加给此发布者/// Implementations of ``Publisher`` must implement this method./// /// The provided implementation of ``Publisher/subscribe(_:)-4u8kn``calls this method.////// - Parameter subscriber: The subscriber to attach to this ``Publisher``, after which it can receive values.func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}extension Publisher {/// Attaches the specified subject to this publisher./// 将指定的 Subject 订阅到一个 Publisher/// - Parameter subject: The subject to attach to this publisher.public func subscribe<S>(_ subject: S) -> AnyCancellable where S : Subject, Self.Failure == S.Failure, Self.Output == S.Output
}extension Publisher {/// Specifies the scheduler on which to receive elements from the publisher.指定用于接收发布者元素的调度器。////// You use the ``Publisher/receive(on:options:)`` operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop.你可以使用 ``Publisher/receive(on:options:)`` 操作符在特定的调度器上接收结果和完成信号,比如在主运行循环上执行 UI 工作。////// In contrast with ``Publisher/subscribe(on:options:)``, which affects upstream messages, ``Publisher/receive(on:options:)`` changes the execution context of downstream messages. 与影响上游消息的 ``Publisher/subscribe(on:options:)`` 相比,``Publisher/receive(on:options:)`` 改变的是下游消息的执行上下文。////// - Parameters:///   - scheduler: The scheduler the publisher uses for element delivery.发布者用于传递元素的调度器。///   - options: Scheduler options used to customize element delivery.用于自定义元素传递的调度器选项。/// - Returns: A publisher that delivers elements using the specified scheduler.返回一个使用指定调度器传递元素的发布者。public func receive<S>(on scheduler: S, options: S.SchedulerOptions? = nil) -> Publishers.ReceiveOn<Self, S> where S : Scheduler
}... 其他很多 Publisherextension ...

Publisher 通过 receive<S>(subscriber: S) 来接受订阅。

一个发布者可以发布多个值,可以有两种可能的状态:成功或失败。在成功状态下,发布者会发送 Output 类型的值;在失败状态下,发布者则会发送 Failure 类型的错误。如果发布者不会失败,Failure 类型通常会被设置为 Never,表示不会产生错误。

Combine 框架内置了很多发布者,包括 JustFutureDeferredEmptyFailRecord 以及 PassthroughSubjectcurValueSubject

一些 Publisher 举例:

  1. Future

    • 用于表示一个异步操作,该操作最终会产生一个值或一个错误。

    • 在以下例子中模拟一个异步操作,随机决定成功或失败。

      let futureP = Future<Int, Error> { promise inDispatchQueue.main.asyncAfter(deadline: .now() + 1) {let success = Bool.random()if success {promise(.success(2))} else {promise(.failure(NSError(domain: "ExampleError", code: 0, userInfo: nil)))}}
      }
      
  2. Just

    • 创建一个只发出单个值然后立即完成的 Publisher。

      let justP = Just("Hello, World!")
      
  3. Deferred

    • 延迟创建 Publisher 直到有订阅者订阅。

      let deferredPublisher = Deferred {Just("Hello, World!")
      }
      

ConnectablePublisher

ConnectablePublisher 可以让开发者控制数据流的开始发送时机。通常情况下,Publisher 在有订阅者时会立即开始发送数据,但 ConnectablePublisher 可以通过调用 connect() 来显式启动数据流。这在需要同步多个订阅者的场景中非常有用,例如网络请求或数据库查询。

比如当多个订阅者订阅了同一个非 ConnectablePublisherPublisher,有可能会出现其中一个订阅者收到了订阅内容,而另外一个订阅者却没收到的情况。这时候就可以使用 使用 makeConnectable()connect() 控制发布。

import Combineclass PublisherExample {private var cancellables = Set<AnyCancellable>()func demonstratePublishers() {// 1. Publisherprint("🍎普通 Publisher🍎")let numbers = PassthroughSubject<Int, Never>()// 第一个订阅者numbers.map { value -> Int inprint("处理数据: \(value)")return value * 2}.sink { value inprint("订阅者A: \(value)")}.store(in: &cancellables)// 第二个订阅者numbers.map { value -> Int inprint("处理数据: \(value)")return value * 2}.sink { value inprint("订阅者B: \(value)")}.store(in: &cancellables)// 发送数据numbers.send(1)numbers.send(2)// 2. ConnectablePublisherprint("\n🍎ConnectablePublisher🍎")let subject = PassthroughSubject<Int, Never>()let connectablePublisher = subject.map { value -> Int inprint("处理数据: \(value)")return value * 2}.makeConnectable()// 第一个订阅者connectablePublisher.sink { value inprint("订阅者A: \(value)")}.store(in: &cancellables)// 第二个订阅者connectablePublisher.sink { value inprint("订阅者B: \(value)")}.store(in: &cancellables)// 连接发布者connectablePublisher.connect().store(in: &cancellables)// 发送数据subject.send(1)subject.send(2)}
}let exp = PublisherExample()
exp.demonstratePublishers()/* 输出:
🍎普通 Publisher🍎
处理数据: 1
订阅者A: 2
处理数据: 1
订阅者B: 2
处理数据: 2
订阅者A: 4
处理数据: 2
订阅者B: 4🍎ConnectablePublisher🍎
处理数据: 1
订阅者B: 2
订阅者A: 2
处理数据: 2
订阅者B: 4
订阅者A: 4
*/
  • 普通 Publisher:
    • 每个订阅者订阅时都会触发一次完整的数据流
    • 耗时操作会被执行多次
    • 订阅者获得的是独立的数据流
  • ConnectablePublisher:
    • 在调用 connect() 之前不会开始发送数据
    • 所有订阅者共享同一个数据流
    • 耗时操作只执行一次
    • 适合需要等待所有订阅者准备就绪才开始发送数据的场景

引用共享

在 Combine 中,引用共享通常是指多个订阅者(Subscriber)共享同一个发布者(Publisher)的输出,而不是每个订阅者都触发一次数据生成。这可以通过 .share() 操作符实现。

关于 ConnectablePublisher 和引用共享所使用的 share 操作符号,在此先不展开讲解,后面讲到操作符的时候再举例说明。

Subject

A publisher that exposes a method for outside callers to publish elements.
一个公开了方法供外部调用者发布元素的发布者。

Subject 是一个特殊的 Publisher。它的特殊之处在于:

  1. 主动发送能力:

    1. 普通的 Publisher 只能在创建时定义数据
    2. Subject 通过 send(_😃 方法可以在外部主动发送值到数据流中
  2. 控制权的不同:

    1. 普通 Publisher 的数据流是由内部逻辑控制的(比如 Just、Future)
    2. Subject 允许外部代码通过 send 方法控制数据流
    // 普通 Publisher:只能在创建时定义数据
    let publisher = [1, 2, 3].publisher  // 数据流固定// Subject:可以随时发送新数据
    let subject = PassthroughSubject<Int, Never>()
    subject.send(1) 
    subject.send(2)
    subject.send(completion: .finished)  // 还可以主动结束
    
public protocol Subject<Output, Failure> : AnyObject, Publisher {/// Sends a value to the subscriber.////// - Parameter value: The value to send.func send(_ value: Self.Output)/// Sends a completion signal to the subscriber.////// - Parameter completion: A `Completion` instance which indicates whether publishing has finished normally or failed with an error.func send(completion: Subscribers.Completion<Self.Failure>)/// Sends a subscription to the subscriber.////// This call provides the ``Subject`` an opportunity to establish demand for any new upstream subscriptions.////// - Parameter subscription: The subscription instance through which the subscriber can request elements.func send(subscription: any Subscription)
}

Combine 内置了两种 Subject,分别是 PassthrougSubjectCurrentValueSubject

PassthroughSubject:

  • 无初始值或当前值存储
  • 只转发接收到的值给订阅者
  • 适用于:
    • 按钮点击等事件触发的场景
    • 不需要保存状态的场景
    • 一次性通知

CurrentValueSubject:

  • 必须提供初始值
  • 维护一个当前值
  • 新订阅者立即收到当前值
  • 可通过 .value 属性读写当前值
  • 适用于:
    • 状态管理(如开关状态)
    • 需要缓存最新值的场景
    • 需要立即知道当前状态的场景
特性PassthroughSubjectCurrentValueSubject
初始化不需要初始值必须提供初始值
值存储不存储值存储最新值
新订阅只接收订阅后的新值立即接收当前值
值访问无法访问当前值可通过 value 属性访问
设置值只能通过 send() 发送值可用 send() 或 value 属性
import Combine
import Foundation// PassthroughSubject
let passthroughSubject = PassthroughSubject<String, Never>()// curValueSubject
let curValueSubject = CurrentValueSubject<String, Never>("初始值")// 订阅 PassthroughSubject
passthroughSubject.sink { value inprint("PassthroughSubject 接收到: \(value)")}// 发送俩值
passthroughSubject.send("第一个值")
passthroughSubject.send("第二个值")// 订阅 curValueSubject
curValueSubject.sink { value inprint("curValueSubject 接收到: \(value)")}// 发送新值
curValueSubject.send("更新后的值")//输出当前值
print("curValueSubject 当前值: \(curValueSubject.value)")// 发送另一个新值
curValueSubject.send("再次更新的值")// 打印当前值
print("curValueSubject 当前值: \(curValueSubject.value)")/* 
输出
PassthroughSubject 接收到: 第一个值
PassthroughSubject 接收到: 第二个值
curValueSubject 接收到: 初始值
curValueSubject 接收到: 更新后的值
curValueSubject 当前值: 更新后的值
curValueSubject 接收到: 再次更新的值
curValueSubject 当前值: 再次更新的值
*/

结语

Publisher 是 Combine 框架的基础组件之一,它为数据流的生成和传递提供了灵活而强大的工具。通过学习 Publisher 的工作原理和使用方式,开发者可以更有效地管理应用中的数据流。在下一篇文章中,将继续探讨介绍 Combine 中的订阅者(Subscriber)机制,进一步完善 Combine 知识体系。

  • Swift Combine 学习(三):Subscription和 Subscriber

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

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

相关文章

C++【内存管理】

C/C中程序的内存划分&#xff1a; 栈&#xff1a;又称堆栈&#xff0c;存放非静态的局部变量、函数参数、返回值等等&#xff0c;栈是向下增长的。内存映射段&#xff1a;是高效的&#xff29;&#xff0f;&#xff2f;映射方式&#xff0c;用于装载一个共享的动态内存库。用户…

手机租赁平台开发助力智能设备租赁新模式

内容概要 手机租赁平台开发&#xff0c;简单说就是让你用得起高大上的智能设备&#xff0c;不管是最新款的手机、平板&#xff0c;还是那些炫酷的智能耳机&#xff0c;这个平台应有尽有。想要体验但又不希望花大钱&#xff1f;那你就找对地方了&#xff01;通过灵活的租赁方案…

【开源免费】基于SpringBoot+Vue.JS校园社团信息管理系统(JAVA毕业设计)

本文项目编号 T 107 &#xff0c;文末自助获取源码 \color{red}{T107&#xff0c;文末自助获取源码} T107&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…

【鸿蒙NEXT】鸿蒙里面类似iOS的Keychain——关键资产(@ohos.security.asset)实现设备唯一标识

前言 在iOS开发中Keychain 是一个非常安全的存储系统&#xff0c;用于保存敏感信息&#xff0c;如密码、证书、密钥等。与 NSUserDefaults 或文件系统不同&#xff0c;Keychain 提供了更高的安全性&#xff0c;因为它对数据进行了加密&#xff0c;并且只有经过授权的应用程序才…

使用npm包的工程如何引入mapboxgl-enhance/maplibre-gl-enhance扩展包

作者&#xff1a;刘大 前言 在使用iClient for MapboxGL/MapLibreGL项目开发中&#xff0c;往往会对接非EPSG:3857坐标系的地图&#xff0c;由于默认不支持&#xff0c;因此需引入mapboxgl-enhance/maplibre-gl-enhance扩展包。 在使用Vue等其他框架&#xff0c;通过npm包下载…

应急指挥系统总体架构方案

引言 应急指挥系统总体架构方案旨在构建一个高效、智能的应急管理体系&#xff0c;以应对自然灾害、事故灾难等突发事件&#xff0c;保障人民生命财产安全。 背景与挑战 近年来&#xff0c;安全生产形势严峻&#xff0c;自然灾害事故频发&#xff0c;对应急指挥系统的要求越…

如何用CSS3创建圆角矩形并居中显示?

在网页设计中&#xff0c;圆角矩形因其美观和现代感而被广泛使用&#xff0c;居中显示元素也是一个常见的需求。今天&#xff0c;我们将学习如何使用CSS3的border-radius属性来创建圆角矩形&#xff0c;并将其居中显示在页面上。 如果你正在学习CSS&#xff0c;那么这个实例将非…

UE5通过蓝图节点控制材质参数

通过蓝图节点控制材质的参数 蓝图节点 在材质上设置标量值 和 在材质上设置向量参数值 Set Scalar Parameter Value on Materials Set Vector Parameter Value on Materials 这两个蓝图节点都可以在蓝图中&#xff0c;控制材质的参数值和向量值

canvas+fabric实现时间刻度尺(二)

前言 我们前面实现了时间刻度尺&#xff0c;鼠标移动显示时间&#xff0c;接下来我们实现鼠标点击某个时间进行弹框。 效果 实现 1.监听鼠标按下事件 2.编写弹框页面 3.时间转换 <template><div><canvas id"rulerCanvas" width"1200"…

手机实时提取SIM卡打电话的信令声音-双卡手机来电如何获取哪一个卡的来电

手机实时提取SIM卡打电话的信令声音 --双卡手机来电如何获取哪一个卡的来电 一、前言 前面的篇章《手机实时提取SIM卡打电话的信令声音-智能拨号器的双SIM卡切换方案》中&#xff0c;我们论述了局域网SIP坐席通过手机外呼出去时&#xff0c;手机中主副卡的呼叫调度策略。 但…

离线语音识别+青云客语音机器人(幼儿园级别教程)

1、使用步骤 确保已安装以下库&#xff1a; pip install vosk sounddevice requests pyttsx3 2、下载 Vosk 模型&#xff1a; 下载适合的中文模型&#xff0c;如 vosk-model-small-cn-0.22。 下载地址&#xff1a; https://alphacephei.com/vosk/models 将模型解压后放置在…

Streaming Dense Video Captioning

原文出处 CVPR 原文链接 [2404.01297] Streaming Dense Video Captioninghttps://arxiv.org/abs/2404.01297 原文笔记 What 1、提出了一种基于聚类传入token的新记忆模块&#xff0c;该模块可以处理任意长的视频&#xff0c;并且可以在不访问视频所有帧的情况下处理视频(…

TCP 连接:三次握手与四次挥手

TCP 协议&#xff0c;全称为“传输控制协议”。 1. TCP 协议段格式 给出几个定义 &#xff1a; 16位源端口号 &#xff1a;用于标识发送端的应用程序。 16位目的端口号 &#xff1a;用于标识接收端的目标应用程序。 32位序号 &#xff1a;用于标识发送的每一个字节流中的第一…

IDEA+Docker一键部署项目SpringBoot项目

文章目录 1. 部署项目的传统方式2. 前置工作3. SSH配置4. 连接Docker守护进程5. 创建简单的SpringBoot应用程序6. 编写Dockerfile文件7. 配置远程部署 7.1 创建配置7.2 绑定端口7.3 添加执行前要运行的任务 8. 部署项目9. 开放防火墙的 11020 端口10. 访问项目11. 可能遇到的问…

redis开发与运维-redis0401-补充-redis流水线与Jedis执行流水线

文章目录 【README】【1】redis流水线Pipeline【1.1】redis流水线概念【1.2】redis流水线性能测试【1.2.1】使用流水线与未使用流水线的性能对比【1.2.2】使用流水线与redis原生批量命令的性能对比【1.2.3】流水线缺点 【1.3】Jedis客户端执行流水线【1.3.1】Jedis客户端执行流…

Uncaught ReferenceError: __VUE_HMR_RUNTIME__ is not defined

Syntax Error: Error: vitejs/plugin-vue requires vue (>3.2.13) or vue/compiler-sfc to be present in the dependency tree. 第一步 npm install vue/compiler-sfc npm run dev 运行成功&#xff0c;本地打开页面是空白&#xff0c;控制台报错 重新下载了vue-loa…

【微服务】【Sentinel】认识Sentinel

文章目录 1. 雪崩问题2. 解决方案3. 服务保护技术对比4. 安装 Sentinel4.1 启动控制台4.2 客户端接入控制台 参考资料: 1. 雪崩问题 微服务调用链路中的某个服务故障&#xff0c;引起整个链路中的所有微服务都不可用&#xff0c;这就是雪崩。动图演示&#xff1a; 在微服务系统…

STM32完全学习——使用定时器1精确延时

一、定时器的相关配置 首先一定要是递减定时器&#xff0c;递增的不太行&#xff0c;控制的不够准确&#xff0c;其次在大于10微秒的延时是非常准确的&#xff0c;小于的话&#xff0c;就没有那没准&#xff0c;但是凑合能用。误差都在一个微秒以内。使用高级定时器也就是时钟…

connect to host github.com port 22: Connection timed out 的解决方法

原因是 Github 被 GFW 屏蔽了。 Windows 系统&#xff0c;打开 C:\Windows\System32\drivers\etc&#xff0c;复制其中的 hosts 文件至桌面&#xff0c;用文本编辑器或者其他工具打开。 复制以下内容进去&#xff1a; 140.82.114.4 github.com 151.101.1.6 github.global.ss…

常见的排序算法过程和比较分析

比较分析 排序类别排序算法时间复杂度&#xff08;最好&#xff09;时间复杂度&#xff08;最坏&#xff09;时间复杂度&#xff08;平均&#xff09;辅助空间复杂度稳定性插入排序直接插入排序O(n)O(n)O(n)O(1)稳定插入排序折半插入排序O(n)O(n)O(n)O(1)稳定插入排序希尔排序…