RxSwift之Subjects
ofollow,noindex">RxSwift-Reactive Programming with Swift (Swift4.0)
前言
上一篇 《iOS開發進階-RxSwift之Observables》學習了 Observables
概念,如何建立,訂閱以及清除等。 Observables
在執行時將值新增到序列中,在將來的某個時機發射給訂閱者。 Subjects
與 Observables
不同在於它同時擔任序列和訂閱者兩個角色。
Subjects
本節將會學習不同型別的 Subject
,各個型別 Subject
是如何工作的及它們之間的不同點。
在 RxSwift
中,提供了四種不同型別的 Subject
。分別如下:
- ①、
PublishSubject
:初始化為空,只發射最新的元素給訂閱者。 - ②、
BehaviorSubject
:有初始值,並且重複發射最晚一個元素給訂閱者。 - ③、
ReplaySubject
:存在一個快取區,重複發射符合快取個數的元素給新的訂閱者。 - ④、
Variable
:是BehaviorSubject
的包裝。
PublishSubjects
當只想將新的事件釋出給訂閱者時, PublishSubject
就派上用場了。它也是通過 .completed
或 .error
事件終止。
下面看一張圓珠圖。 最上方的線代表 PublishSubject
。第二條和第三條代表訂閱者。向上的虛線箭頭代表訂閱,向下的虛線箭頭代表發射事件。
- 第一個訂閱者在
1)
之後,只能接收到2)
和3)
。 - 第二個訂閱者在
2)
之後,只能接收到3)
。
建立PublishSubject
建立 PublishSubject
,示例程式碼如下:
example(of: "PublishSubject") { // 1 let subject = PublishSubject<String>() // 2 subject.onNext("1") // 3 let subscriptionOne = subject.subscribe(onNext: { string in print(string) }) // 4 subscriptionOne.dispose() }
- 建立一個包含字串型別的
PublishSubject
。 - 向
subject
中新增一個字串,此時並沒有任何值輸出因為沒有訂閱。 - 為
subject
建立一個訂閱,用於列印.next
事件。但是此時並沒有列印。 - 用於銷燬回收。
由於 PublishSubject
只能發射在訂閱之後新增的事件,所以上面並沒有輸出結果。在 4)
之前新增下面這行程式碼:
// subject.on(.next("2")) // 或者 subject.onNext("2")
subject
之前添加了訂閱,所以輸出:
--- Example of: PublishSubject --- 1) 2
接下來在新增一個訂閱,新增如下程式碼:
let subscriptionTwo = subject.subscribe { event in print("2)", event.element ?? event) }
這種方式訂閱逃逸閉包引數是 Event<String>
型別,事件中 element
屬性是可選值,所以通過 ??
操作取值,如果非 nil
列印值,否則列印事件本身。
接下來向 subject
中新增新的元素
subject.onNext("3")
輸出結果:
--- Example of: PublishSubject --- 1) 2 1) 3 2) 3
通過上面的驗證,結果與圓珠圖中相同。
在 PublishSubject
當接收到 .completed
和 .error
事件也會終止。例如:
subject.onCompleted() subject.onNext("4")
此時控制檯只打印出 completed
並沒有輸出 4
,因為 subject
已經結束。 事實上,任何型別的 subject
一旦終止,將不會再發射事件。
BehaviorSubjects
BehaviorSubjects
工作方式類似於 PublishSubjects
,不同點是它會重新發射最晚的 .next
事件給新新增的訂閱者。
對應的圓珠圖如下:
- 第一條線是當前的
subject
。 - 第一個訂閱者,在
①
之後新增由於BehaviorSubjects
可以重新發射最晚的一個元素給新的訂閱者,所以接收到事件序列是① ② ③
。 - 同理第二個訂閱,可以接收的事件序列
② ③
。
示例程式碼:
// 錯誤列舉 enum LEError: Error { case anError } // 封裝列印方法 func print<T: CustomStringConvertible>(label: String, event: Event<T>) { print(label, event.element ?? event) } // 特點:監聽上一個事件 example(of: "BehaviorSubject") { // 1. 建立一個BehaviorSubject let subject = BehaviorSubject(value: "1") let disposeBag = DisposeBag() // 2. 第一個訂閱者 subject.subscribe { print(label: "1)", event: $0) } .disposed(by: disposeBag) // 3 subject.onNext("2") // 4 // subject.onError(LEError.anError) // 1) 輸出Error // subject.onCompleted() // 5 subject.subscribe { print(label: "2)", event: $0) // 輸出:error } .disposed(by: disposeBag) // 6 subject.onNext("3") }
- 建立
subject
和disposeBag
。 - 新增第一個訂閱,此時會輸出初始
1) 1
。 - 新增新的元素
2
,定義訂閱輸出2
。 - 兩種結束
subject
的方式,當出現錯誤時終止或者傳送complete
時終止。如果終止後續就不會再發送新的事件。 - 第二個訂閱,此時輸出
2
即新增訂閱之前最晚的一個元素。 - 新增新元素
3
,此時兩個訂閱都會輸出值。
ReplaySubjects
ReplaySubject
存在一個臨時的快取,會重複將快取中的元素髮射給新的訂閱。對應的圓珠圖如下:

第一個訂閱者(中間線),第二個訂閱(下方線)。兩者的輸出結果都是序列 ① ② ③
。
示例程式碼如下:
example(of: "ReplaySubject") { // 建立Subject,快取大小為2。每次會將最晚的兩個傳送給新的訂閱 let subject = ReplaySubject<String>.create(bufferSize: 2) let disposeBag = DisposeBag() // 訂閱① subject.subscribe { print(label: "1)", event: $0) } .disposed(by: disposeBag) // 新增元素 subject.onNext("1") subject.onNext("2") // 訂閱② subject.subscribe { print(label: "2)", event: $0) } .disposed(by: disposeBag) // 新增元素 subject.onNext("3") }
輸出結果:
--- Example of: ReplaySubject --- 1) 1 1) 2 2) 1 2) 2 1) 3 2) 3
這裡不做過多的解釋,如果理解前兩個這個不難理解。
由於 ReplaySubjects
的快取是儲存在記憶體中的,所以在使用的時候需要注意大小問題,防止浪費過的的記憶體。儘量避免建立過多的快取或存放大的物件。
Variables
在之前有提到, Variable
是對 BehaviorSubject
的包裝,它會儲存當前的值作為狀態。可以通過 value
屬性訪問當前的值,也可以通過它修改值。通過 value
屬性可以代替 onNext(_:)
。
Variable
包含 BehaviorSubject
的所有功能,存在初始值,可以重複發射最晚元素給新的訂閱。不過,想訪問 Variable
中包裝的 BehaviorSubject
需要使用 asObservable()
方法。
Variable
與其他 subject
不同點,第一,不能向 Variable
中新增 .error
事件但是可以監聽。在銷燬時自動傳送 completed
事件,不需要手動新增。
example(of: "Variable") { let variable = Variable("Init Variable") let disposeBag = DisposeBag() // 1 variable.value = "New init value" // 新增新值 // 2 variable.asObservable() .subscribe { print(label: "1)", event: $0) // 輸出前一個值 } .disposed(by: disposeBag) // 2 variable.value = "1" variable.asObservable() .subscribe { print(label: "2)", event: $0) } .disposed(by: disposeBag) // 3 variable.value = "2" }
輸出結果:
--- Example of: Variable --- 1) New init value 1) 1 2) 1 1) 2 2) 2
小結
本節學習了四種 Subjects
概念及建立方法。理解基礎概念才能為以後的熟練運用打好基礎。