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,一经查实,立即删除!

相关文章

Win11清除安全中心保护历史记录全攻略

Win11清除安全中心保护历史记录全攻略 在Windows 11操作系统中,安全中心作为守护系统安全的重要防线,扮演着举足轻重的角色。它不仅实时监控系统的安全状态,还详细记录各类安全事件,为用户提供全面的安全保障。然而,随着系统的长期使用,这些安全记录可能会逐渐累积,占用…

C++【内存管理】

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

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

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

Spring Boot 3 文件上传、多文件上传、大文件分片上传、文件流处理以及批量操作

在 Spring Boot 3 中&#xff0c;可以通过内置的文件处理机制结合 Java 的 IO 流与多线程技术&#xff0c;实现文件上传、多文件上传、大文件分片上传、文件流处理以及批量操作的需求。以下是详细实现步骤&#xff1a; 1. 单文件上传 控制器代码 import org.springframework…

【开源免费】基于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;并且只有经过授权的应用程序才…

Vite:新时代前端构建工具的最佳选择

Vite&#xff1a;新时代前端构建工具的最佳选择 随着前端技术的快速发展&#xff0c;传统的构建工具&#xff08;如 Webpack&#xff09;因其较慢的构建速度和复杂的配置逐渐暴露出局限性。Vite 作为一款基于现代浏览器的快速构建工具&#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;那么这个实例将非…

spring boot 异步线程池的使用

创建Spring Boot项目 首先&#xff0c;你需要创建一个Spring Boot项目。你可以使用Spring Initializr&#xff08;https://start.spring.io/&#xff09;来快速生成项目结构。 添加异步支持依赖 在你的pom.xml文件中&#xff0c;确保你已经添加了Spring Boot的starter依赖&…

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. 可能遇到的问…

【机器学习】回归

文章目录 1. 如何训练回归问题2. 泛化能力3. 误差来源4. 正则化5. 交叉验证 1. 如何训练回归问题 第一步&#xff1a;定义模型 线性模型&#xff1a; y ^ b ∑ j w j x j \hat{y} b \sum_{j} w_j x_j y^​b∑j​wj​xj​ 其中&#xff0c;( w ) 是权重&#xff0c;( b )…

C++ 设计模式:状态模式(State Pattern)

链接&#xff1a;C 设计模式 链接&#xff1a;C 设计模式 - 备忘录 状态模式&#xff08;State Pattern&#xff09;是一种行为设计模式&#xff0c;它允许对象在内部状态改变时改变其行为。状态模式将状态的行为封装在独立的状态类中&#xff0c;并将状态的切换逻辑委托给这些…