Swift Combine — Publisher、Operator、Subscriber概念介绍

Combine框架介绍

Combine框架提供了一个声明式Swift API,用于处理随时间变化的值。这些值可以表示多种异步事件。Combine声明发布者公开发布可能随时间变化的值,并声明订阅者从发布者接收这些值。

Combine框架为应用程序如何处理事件提供了一种声明式的方法。可以为给定的事件源创建单个处理链,而不是潜在地实现多个委托回调或完成处理闭包。一个事件及其对应的数据被发布出来,最后被订阅者消化和使用,期间这些事件和数据需要通过一系列操作变形,成为我们最终需要的事件和数据。

Combine中最重要的角色有三种,恰好对应了这三种操作:负责发布事件的Publisher,负责订阅事件的Subscriber,以及负责转换事件和数据的Operator

Publisher 发布者

Publisher是一个基础协议,它代表事件的发布者,其他各式各样的Publisher都继承了这个基础Publisher协议。Publisher协议的定义也比较简单,包括两个关联类型(associatedtype)和一个receive方法。

swift 复制代码
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.
    associatedtype 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
}

Publisher 最主要的工作其实有两个:发布新的事件及其数据,以及准备好被Subscriber订阅。
Output 定义了某个 Publisher 所发布的值的类型,Failure 则定义可能产生的错误的类型。随着时间的推移,事件流也会逐渐向前发展。对应 OutputFailurePublisher 可以发布三种事件:

  1. 类型为 Output 的新值:这代表事件流中出现了新的值。
  2. 类型为 Failure 的错误:这代表事件流中发生了问题,事件流到此终止。
  3. 完成事件:表示事件流中所有的元素都已经发布结束,事件流到此终止。

Subscriber介绍

Subscriber 也是一个抽象的协议。

swift 复制代码
public protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible {

    /// The kind of values this subscriber receives.
    associatedtype Input

    /// The kind of errors this subscriber might receive.
    ///
    /// Use `Never` if this `Subscriber` cannot receive errors.
    associatedtype Failure : Error

    /// Tells the subscriber that it has successfully subscribed to the publisher and may request items.
    ///
    /// Use the received ``Subscription`` to request items from the publisher.
    /// - Parameter subscription: A subscription that represents the connection between publisher and subscriber.
    func receive(subscription: any Subscription)

    /// Tells the subscriber that the publisher has produced an element.
    ///
    /// - Parameter input: The published element.
    /// - Returns: A `Subscribers.Demand` instance indicating how many more elements the subscriber expects to receive.
    func receive(_ input: Self.Input) -> Subscribers.Demand

    /// Tells the subscriber that the publisher has completed publishing, either normally or with an error.
    ///
    /// - Parameter completion: A ``Subscribers/Completion`` case indicating whether publishing completed normally or with an error.
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

定义中 InputFailure 分别表示了订阅者能够接受的事件流数据类型和错误类型。想要订阅某个 PublisherSubscriber 中的这两个类型必须与 Publisher 的 Output 和 Failure 一致。

Subscribers.Sink是一个简单的订阅者,在订阅时请求无限数量的值。

swift 复制代码
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
public func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable

该方法可以同时提供两个闭包也可以一个,receiveCompletion用来接收 failure 或者 finished 事件,receiveValue 用来接收 output 值。

还有一个 Subscriber 可能会更为简洁常用,那就是 assign。和通过 sink 提供闭包,可以执行任意操作不同,assign 接受一个 class 对象以及对象类型上的某个键路径 (key path)。每当 output 事件到来时,其中包含的值就将被设置到对应的属性上去。

swift 复制代码
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable

使用这个方法有些要求,只有那些 class 类型的实例中的属性能被绑定,在SwiftUI中,我们厂用的和View匹配的ViewModel继承了 ObservableObject,而继承 ObservableObject的只能是class类,因此assign放在常用在这里。

Operator介绍

Operator提供了一些方法,他们接收上有的元素进行操作,然后创建下游发布者或者订阅者。

使用Operator组装一个Publisher链(可以以订阅者结束),该Publisher链处理上游Publisher生成的元素。每个Operator创建并配置PublisherSubscriber的实例,并将其订阅到调用该方法的Publisher上。

说起来不太好理解,看看下面这个例子。

swift 复制代码
let cancellable = [1, 2, 3, 4, 5].publisher
    .filter {
        $0 % 2 == 0
    }
    .sink {
        print ("Even number: \($0)")
    }
// Prints:
// Even number: 2
// Even number: 4

在上面的示例中,一个数组Publisher发出整数1、2、3、4、5。filter操作符创建了一个Publisher,重新发布偶数值。sink操作符创建Subscriber,该Subscriber打印接收到的每个值。sink创建的Subscriber自定订阅filter创建的Publisherfilter创建的Publisher订阅了数组Publisher

写在最后

PublisherOperatorSubscriber 三者组成了从事件发布,变形,到订阅的完整链条。在建立起事件流的响应链后,随着事件发生,app 的状态随之演变,这些是响应式编程处理异步程序的 Combine 框架的基础架构。

相关推荐
大熊猫侯佩3 分钟前
SwiftUI 中如何花样玩转 SF Symbols 符号动画和过渡特效
swiftui·swift·apple
大熊猫侯佩1 小时前
SwiftData 共享数据库在 App 中的改变无法被 Widgets 感知的原因和解决
swiftui·swift·apple
大熊猫侯佩1 小时前
使用令牌(Token)进一步优化 SwiftData 2.0 中历史记录追踪(History Trace)的使用
数据库·swift·apple
大熊猫侯佩1 小时前
SwiftUI 在 iOS 18 中的 ForEach 点击手势逻辑发生改变的解决
swiftui·swift·apple
Daniel_Coder2 小时前
Xcode 16.4 + iOS 18 系统运行时崩溃:___cxa_current_primary_exception 符号丢失的原因与解决方案
ios·xcode·ios 18·dyld·libc++abi
烈焰晴天5 小时前
使用ReactNative加载Svga动画支持三端【Android/IOS/Harmony】
android·react native·ios
sg_knight5 小时前
Flutter嵌入式开发实战 ——从树莓派到智能家居控制面板,打造工业级交互终端
android·前端·flutter·ios·智能家居·跨平台
胖虎115 小时前
iOS上传应用包错误问题 “Invalid bundle. The “UIInterfaceOrientationPortrait”“
ios·审核·苹果审核·苹果传包
安和昂16 小时前
【iOS】ARC 与 Autorelease
macos·ios·cocoa
豪冷啊19 小时前
iOS UIActivityViewController 组头处理
ios·objective-c·xcode