Combine 框架,從0到1 —— 5.Combine 中的 Subjects
阿新 • • 發佈:2020-09-26
本文首發於 [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 無法設定緩衝的大小。也許某天蘋果會對此做出調整吧~