結合: コアコンセプト

前の概要で述べたように、Combine には、パブリッシャー、オペレーター、サブスクライバーという 3 つの主要な概念があります。パブリッシャサブスクライバは、それぞれイベント パブリッシャとサブスクライバを表します。オペレータは両方の特性を持ちます。サブスクライバ プロトコルとパブリッシャ プロトコルに従い、アップストリーム データの操作に使用されます。

これら 3 つの中心的な概念を理解していれば、Combine を非常にうまく使用できるため、この観点から、Combine を次の形式として単純に理解できます。

結合 = パブリッシャー + オペレーター + 購読者

出版社

意味:

public protocol Publisher<Output, Failure> {

    associatedtype Output
    associatedtype Failure : Error

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
复制代码

パブリッシャーはサブスクライブ後、サブスクライバーのリクエストに応じてデータを提供しますが、サブスクライバーのないパブリッシャーはデータを送信しません。

パブリッシャーは次の 3 種類のイベントを発行できます。

  • 出力: イベント ストリームに表示される新しい値
  • 完了: イベント フロー内のすべての要素が解放され、イベント フローはミッションを完了して終了しました。
  • 失敗: イベント フローでエラーが発生したため、イベント フローはここで終了します

Finished イベントと Failure イベントは、Subscribers.Completion で定義されます。

extension Subscribers {
    @frozen public enum Completion<Failure> where Failure : Error {
        case finished
        case failure(Failure)
    }
}
复制代码

3 つのイベントはいずれも必要ありません。パブリッシャーは 1 つ以上の出力値を発行する場合もあれば、発行しない場合もあります。パブリッシャーは決して停止しない場合もあれば、失敗または終了イベントを発行して終了を通知する場合もあります

最終的に終了するイベント ストリームはイベントの有限ストリームと呼ばれますが、失敗または終了しないイベント ストリームはイベントの無限ストリームと呼ばれます。たとえば、ネットワーク リクエストは有限イベント ストリームであり、ボタン クリック イベント ストリームは無限イベント ストリームです。

主題

件名は発行者でもあります

public protocol Subject : AnyObject, Publisher {
	func send(_ value: Self.Output)
	func send(completion: Subscribers.Completion<Self.Failure>)
}
复制代码

Subject 暴露了两个 send 方法,外部调用者可以通过这两个方法来主动地发布 output 值、failure 事件或 finished 事件。Subject可以将传统的指令式编程中的异步事件和信号转换到响应式的世界中去

Combine内置了两种Subject类型:

  • PassthroughSubject

    简单地将 send 接收到的事件转发给下游的其他 Publisher 或 Subscriber,不会持有最新的output;如果在订阅前执行send操作,是无效的。

let publisher1 = PassthroughSubject<Int, Never>()
print("开始订阅")
publisher1.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
    })
publisher1.send(1)
publisher1.send(2)
publisher1.send(completion: .finished)
// 输出:
// 开始订阅
// 1
// 2
// finished
复制代码

调整一下 sink 订阅的时机,将它延后到 publisher.send(1) 之后,那么订阅者将会从 2 的事件开始进行响应:

let publisher2 = PassthroughSubject<Int, Never>()
publisher2.send(1)
print("开始订阅")
publisher2.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
              })
publisher2.send(2)
publisher2.send(completion: .finished)
// 输出:
// 开始订阅
// 2
// finished
复制代码
  • CurrentValueSubject

    会包装和持有一个值,并在设置该值时发送事件并保留新的值。在订阅发生的瞬间,会把当前保存的值发送给订阅者;接下来对值的每次设置都将触发订阅响应。

let publisher3 = CurrentValueSubject<Int, Never>(0)
print("开始订阅")
publisher3.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
    })
publisher3.value = 1
publisher3.value = 2
publisher3.send(completion: .finished)
// 输出:
// 开始订阅
// 0
// 1
// 2
// finished
复制代码

Subscriber

定义:

public protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible {

    associatedtype Input
    associatedtype Failure : Error

    func receive(subscription: Subscription)

    func receive(_ input: Self.Input) -> Subscribers.Demand

    func receive(completion: Subscribers.Completion<Self.Failure>)
}
复制代码

想要订阅某个 Publisher,Subscriber 中的这两个类型必须与 Publisher 的 Output 和 Failure 一致。

Combine 中也定义了几个比较常见的 Subscriber,可以供我们直接使用。

sink

sink的完整函数签名为

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

receiveCompletion 用来接收 failure 或者 finished 事件,receiveValue 用来接收 output 值。

let just = Just("Hello word!")
    _ = just.sink(receiveCompletion: {
        print("Received completion", $0)
    }, receiveValue: {
        print("Received value", $0)
    })
复制代码

如果说Subject提供了一条从指令式异步编程通向响应式世界的道路的话,那么sink就补全了另外一侧。sink可以作为响应式代码和基于闭包的指令式代码之间的桥梁,让你可以通过它从响应式的世界中回到指令式的世界。因为receiveValue闭包会将值带给你,想要对它做什么就随你愿意了。

assign

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

assign 接受一个 class 对象以及对象类型上的某个键路径 (key path)。每当 output 事件到来时,其中包含的值就将被设置到对应的属性上去。

定义一个MyObject类

class MyObject {
	var value: String = "123" {
		didSet {
			print(value)
		}
	}
}
复制代码

使用 assign(to:on:)修改MyObject实例对象属性的值

let obj = MyObject()
let _ = ["456"].publisher.assign(to: \.value, on: obj)
复制代码

assign 还有一个变体, assign(to:) 可将 Publisher 发出的值用于 @Published 属性包装器包装过的属性

class MyObject {
	@Published var value = 0
}

let objc = MyObject()
objc.$value.sink {		
	print($0)
}

(0 ..< 5).publisher.assign(to: &objc.$value)
复制代码

value 属性用 @Published包装,除了可作为常规属性访问之外,它还为属性创建了一个 Publisher。使用 @Published 属性上的 $ 前缀来访问其底层 Publisher,订阅该 Publisher,并打印出收到的每个值。最后,我们创建一个 0..<5 的 Int Publisher 并将它发出的每个值 assign 给 object 的 value Publisher。 使用 & 来表示对属性的 inout 引用,这里的 inout 来源于函数签名:

func assign(to published: inout Published<Self.Output>.Publisher)
复制代码

这里有一个值得注意的地方,如果使用 assign(to: .value, on: self) 并存储生成的 AnyCancellable,可能会引起引用循环:MyObject 类实例持有生成的 AnyCancellable,而生成的 AnyCancellable 同样保持对 MyObject 类实例的引用。因此,推荐使用 assign(to:) 来替代 assign(to:on:) ,以避免此问题的发生,因为assign(to:) ::不返回 AnyCancellable,在内部完成了生命周期的管理,在 @Published 属性释放时会取消订阅。::

Operator

关于Operator的介绍,在概览中已经做了相对详细的介绍。

オペレーターは、上流のパブリッシャーへの入力として使用でき、処理されたデータを下流に出力する新しいパブリッシャーになることもできます。さまざまなオペレーターを組み合わせて処理チェーンを形成できます。チェーンの先頭にあるパブリッシャーがイベントまたはデータをパブリッシュすると、チェーン内のオペレーターがこれらのデータとイベントを段階的に処理し、最終的にサブスクライバーが指定した結果に到達します。

一般的に使用される演算子については、この記事が非常に包括的であり、参考として使用できます。

参考

結合: Swift を使用した非同期プログラミング

SwiftUI と Combine プログラミング

おすすめ

転載: juejin.im/post/7200653914818691131