1. 程式人生 > >Combine 框架,從0到1 —— 5.Combine 中的 Subjects

Combine 框架,從0到1 —— 5.Combine 中的 Subjects

  本文首發於 [Ficow Shen's Blog](https://ficowshen.com),原文地址: [Combine 框架,從0到1 —— 5.Combine 中的 Subjects](https://blog.ficowshen.com/page/post/22)。   ## 內容概覽 - 前言 - PassthroughSubject - CurrentValueSubject - Subject 作為訂閱者 - 常見用法 - 總結   ## 前言   正所謂,工欲善其事,必先利其器。在開始使用 `Combine` 進行響應式程式設計之前,建議您先了解 `Combine` 為您提供的各種釋出者(Publishers)、操作符(Operators)、訂閱者(Subscribers)。 `Subject` 是一類比較特殊的釋出者,因為它同時也是訂閱者。`Combine` 提供了兩類 `Subject` :`PassthroughSubject` 和 `CurrentValueSubject`。   如果您想了解更多 Publishers 的用法和注意事項,可以閱讀:[Combine 框架,從0到1 —— 5.Combine 提供的釋出者(Publishers)](https://blog.ficowshen.com/page/post/21)   ## PassthroughSubject [官網文件](https://developer.apple.com/documentation/combine/passthroughsubject)   `PassthroughSubject` 可以向下遊訂閱者廣播發送元素。使用 `PassthroughSubject` 可以很好地適應指令式程式設計場景。 如果沒有訂閱者,或者需求為0,`PassthroughSubject` 就會丟棄元素。 示例程式碼: ``` swift final class SubjectsDemo { private var cancellable: AnyCancellable? private let passThroughtSubject = PassthroughSubject() func passThroughtSubjectDemo() { cancellable = passThroughtSubject .sink { print(#function, $0) } passThroughtSubject.send(1) passThroughtSubject.send(2) passThroughtSubject.send(3) } } ``` 輸出內容: > passThroughtSubjectDemo() 1 passThroughtSubjectDemo() 2 passThroughtSubjectDemo() 3   ## CurrentValueSubject [官網文件](https://developer.apple.com/documentation/combine/currentvaluesubject)   `CurrentValueSubject` 包裝一個值,當這個值發生改變時,它會發佈一個新的元素給下游訂閱者。 `CurrentValueSubject` 需要在初始化時提供一個預設值,您可以通過 `value` 屬性訪問這個值。在呼叫 `send(_:)` 方法傳送元素後,這個快取值也會被更新。 示例程式碼: ``` swift final class SubjectsDemo { private var cancellable: AnyCancellable? private let currentValueSubject = CurrentValueSubject(1) func currentValueSubjectDemo() { cancellable = currentValueSubject .sink { [unowned self] in print(#function, $0) print("Value of currentValueSubject:", self.currentValueSubject.value) } currentValueSubject.send(2) currentValueSubject.send(3) } } ``` 輸出內容: > currentValueSubjectDemo() 1 Value of currentValueSubject: 1 currentValueSubjectDemo() 2 Value of currentValueSubject: 2 currentValueSubjectDemo() 3 Value of currentValueSubject: 3   ## Subject 作為訂閱者   示例程式碼: ``` swift final class SubjectsDemo { private var cancellable1: AnyCancellable? private var cancellable2: AnyCancellable? private let optionalCurrentValueSubject = CurrentValueSubject(nil) private func subjectSubscriber() { cancellable1 = optionalCurrentValueSubject .sink { print(#function, $0) } cancellable2 = [1, 2, 3].publisher .subscribe(optionalCurrentValueSubject) } } ``` `optionalCurrentValueSubject` 可以作為一個訂閱者去訂閱序列釋出者 `[1, 2, 3].publisher` 傳送的元素。 輸出內容: > subjectSubscriber() nil subjectSubscriber() Optional(1) subjectSubscriber() Optional(2) subjectSubscriber() Optional(3)   ## 常見用法   在使用 `Subject` 時,我們往往不會將其暴露給呼叫方。這時候,可以使用 `eraseToAnyPublisher` 操作符來隱藏內部的 `Subject`。 示例程式碼: ``` swift struct Model { let id: UUID let name: String } final class ViewModel { private let modelSubject = CurrentValueSubject(nil) var modelPublisher: AnyPublisher { return modelSubject.eraseToAnyPublisher() } func updateName(_ name: String) { modelSubject.send(.init(id: UUID(), name: name)) } } ``` 外部呼叫者無法直接操控 `ViewModel` 內部的 `Subject`,這樣可以讓 `ViewModel` 更好地面對將來可能的改動。 外部呼叫者只需要知道 `modelPublisher` 是 `AnyPublisher` 型別的釋出者即可,無論內部採用了 `CurrentValueSubject` 還是 `PassthroughSubject` 甚至是其他的釋出者。   ## 總結   相比於其他的釋出者來說, `Subject` 是比較容易理解的,而且也是最常用到的。 只可惜,對比 `Rx` 提供的 Subject,Combine 中的 Subject 無法設定緩衝的大小。也許某天蘋果會對此做出調整吧~