Combine 学习笔记(二):Publisher 与 Subscriber 的基本概念

前一篇文章我们介绍了响应式编程的基本概念并且简要介绍了一下 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
 */

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注