前一篇文章我们介绍了响应式编程的基本概念并且简要介绍了一下 Combine 框架。本文我们就开始分析 Combine 中的数据发布者(Publisher)与订阅者(Subscriber)。
一、Publisher 简介与定义
Publisher 的主要工作是随着时间推移向一个或多个 Subscriber 发布数据或者事件。本质上说,Publisher 的工作其实仅有两个——被 Subscriber 订阅、发布数据和事件。
初学者往往会走入一个误区,认为 Publisher会事先准备好数据和事件然后等待 Subscriber来取,其实不然。实际上只有当 Subscriber订阅 Publisher后告诉 Publisher自己需要什么数据,Publisher才会生成什么数据给 Subscriber,这点贯穿着 Combine的始终,在学习时我们要特别注意。
Combine 的文档给出 Publisher 的定义如下:
public protocol Publisher { /// 发布的数据类型 associatedtype Output /// 失败的错误类型 associatedtype Failure : Error /// Subscriber不会主动调用该方法,而是在调用subscribe(_:)方法时内部调用此方法 func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input } extension Publisher { /// 将指定的Subscriber订阅到此Publisher /// 供外部调用,调用此方法会自动调用上面的receive(subscriber:) public func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input }
● Output 及 Failure 定义了 Publisher 所发布的数据的类型和失败的错误类型。如果不会失败,Failure 使用 Never。
● Publisher 只能发布一个结束事件,一旦发出了其生命周期就结束了,不能再发出任何数据和事件。
● Subscriber 调用 subscribe (_:) 方法订阅 Publisher 时会调用 receive<S>(subscriber: S) 方法。它规定:Publisher 的 Output 必须与 Subscriber 的 Input 类型匹配,Failure 也是如此。
二、Combine 内置的 Publisher
Combine 为我们内置了一些 Publisher 以满足我们开发的需要。一般情况下内置的 Publisher 已经够用,不需要再自定义了,Apple 也不推荐我们这么做。下面列出了一些常用的 Publisher 以供参考:
● Just:只提供一个数据然后终止的 Publisher,失败类型为 Never。
● Future:异步操作的 Publisher,用一个闭包初始化,该闭包最终解析为单个输出数据或失败。
● Empty:一个从不发布任何数据的 Publisher ,并且可以选择立即完成。
● Fail:立即使用指定错误终止的 Publisher 。
● Optional:如果可选数据具有数据,则 Publisher 仅向每个 Subscriber 发布一次可选数据。
● Sequence:发布给定数据序列的 Publisher 。
● Deferred:在运行提供的闭包之前等待订阅的 Publisher ,以便为新的 Subscriber 创建 Publisher 。
● Record:允许记录一系列 Input 和 Completion,供每个 Subscriber 回放。
● Share:实现者为类的 Publisher ,其行为与其上游 Publisher 相同。
● Multicast:多播 Publisher ,当有多个 Subscriber,但希望上游 Publisher 的每个数据仅调用一次 receive (_:) 时使用。
● ObservableObject:配合 SwiftUI 一起使用,符合 ObservableObject 协议的对象可以提供 Publisher。
● @Published:属性包装器,用来把一个属性数据转变为 Publisher。
上面的 Publisher 较为常用的有 Just、Future、Sequence、ObservableObject 和 @Publishered,本系列文章将会着重讲解这些 Publisher,剩下的 Publisher 读者可以根据需要自行查阅官方文档。
三、Subscriber 简介与定义
Publisher 根据 Subscriber 的请求提供数据。上文我们讨论 Publisher 的定义时提到,如果没有任何订阅请求,Publisher 不会主动发布任何数据。所以说,Subscriber 负责向 Publisher 请求数据并接收数据(也可能会获取失败)。
Combine 的文档给出 Subscriber 的定义如下:
public protocol Subscriber: CustomCombineIdentifierConvertible { /// 接收数据的类型 associatedtype Input /// 可能接收到失败的错误类型 associatedtype Failure: Error /// 收到了订阅成功的消息,可以开始向Publisher请求数据了 func receive(subscription: Subscription) /// 收到了Publisher产生的值的消息,Publisher发送的数据已经到达了Subscriber func receive(_ input: Self.Input) -> Subscribers.Demand /// 收到了Publisher产生已经终止的消息,Publish发布了完成事件 func receive(completion: Subscribers.Completion<Self.Failure>) }
同 Publisher 类似,Input 和 Failure 分别表示了 Subscriber 能够接收的数据类型和失败的错误类型。若不会接收失败,则 Failure 使用 Never。
四、Combine 内置的 Subscriber
与 Publisher 不同,我们可以自定义 Subscriber 来满足开发的需要。Combine 也为我们提供了内置的 Subscriber,下面就来介绍两个较为典型的内置 Subscriber。
Sink
Sink 在闭包中处理收到的数据或者 completion 事件。每当收到新值时,就会调用 receiveValue 闭包。处理 completion 事件的 receiveCompletion 闭包是可选的,这个闭包会在接收到 Publisher 终止的消息后调用。
import Combine // 发送单个数据 let publisher = Just(1) //进行订阅 let subscription = publisher.sink(receiveCompletion: { _ in print("receiveCompletion") }, receiveValue: { value in print(value) }) /* 输出: 1 receiveCompletion */
Assign
Assign 可以通过传入类中某个属性的 KeyPath,来将这个属性的值设置为 Publisher 的 Output。通过 Assign,我们可以直接把发布的值绑定到数据模型或者 UI 控件的属性上。
import Combine // 创建对象 class Test { var str: String = "" } let obj = Test() print(obj.str) let publisher = Just("Louyu") // assign订阅,在to中传入KeyPath设置到obj对象的str属性上 publisher.assign(to: \.str, on: obj) print(obj.str) /* 输出: Louyu */
在 iOS 14 后我们可以通过给 str 添加 @Published 来免于考虑 KeyPath 的问题:
import Combine // 创建对象 class Test { @Published var str: String = "" } let obj = Test() print(obj.str) let publisher = Just("Louyu") // assign订阅,设置到Test的@Published属性上 publisher.assign(to: &obj.$str) //&和$不能省 print(obj.str) /* 输出: Louyu */
五、Subscriber 自定义
下面我们来自定义一个 Subscriber,来对 Publisher 和 Subscriber 之间的关系有一个更加深入的认识。
import Combine // 创建一个Publisher let publisher = [1, 2, 3, 4, 5, 6].publisher // 自定义Subscriber class CustomSubscriber: Subscriber { // 指定接收值的类型与失败的类型 typealias Input = Int typealias Failure = Never // Publisher会首先调用该方法 func receive(subscription: Subscription) { // 接收订阅的值的最大量,通过.max来设置最大值,.unlimited则为不设限 subscription.request(.max(6)) } // 接收到值时调用的方法,返回接收值最大个数的变化 func receive(_ input: Int) -> Subscribers.Demand { // 输出接收到的值 print("Received value", input) // 若返回.max(1),则下一次接收的最大数量为6+1=7,接收到最大量是累加的。返回.none意思是不改变最大接收值的数量(永远为上面方法设置的大小,若上面设置的最大值小于Publisher发送的数据,则不会走completion),而不是不接收值 return .none } // 实现接收完成事件的方法 func receive(completion: Subscribers.Completion<Never>) { print("Received completion", completion) } } // 订阅Publisher publisher.subscribe(CustomSubscriber()) /* 输出 Received value 1 Received value 2 Received value 3 Received value 4 Received value 5 Received value 6 Received completion finished */