
更新:
2019.3.26RxSwift v4.4Swift 4.2
前言
上一篇 《iOS開發(fā)進階-RxSwift之Observables》學習了Observables概念,如何創(chuàng)建,訂閱以及清除等。Observables在運行時將值添加到序列中,在將來的某個時機發(fā)射給訂閱者。Subjects與Observables不同在于它同時擔任序列和訂閱者兩個角色。
Subjects
本節(jié)將會學習不同類型的Subject,各個類型Subject是如何工作的及它們之間的不同點。
在RxSwift中,提供了四種不同類型的Subject 和兩種解包類型。分別如下:
- ①、
PublishSubject:初始化為空,只發(fā)射最新的元素給訂閱者。 - ②、
BehaviorSubject:有初始值,并且重復發(fā)射最晚一個元素給訂閱者。 - ③、
ReplaySubject:存在一個緩存區(qū),重復發(fā)射符合緩存?zhèn)€數(shù)的元素給新的訂閱者。 - ④、
Variable:是BehaviorSubject的包裝。 - ⑤、
AsyncSubject: 只有在接收到.completed事件時,發(fā)射最后一個.next事件。這個類型Subject很少使用。 - ⑥、
PublishRelay和BehaviorRelay:包裝相關的Subject,只接受.next事件。
PublishSubjects
當只想將新的事件發(fā)布給訂閱者時,PublishSubject就派上用場了。它也是通過.completed或.error事件終止。
下面看一張圓珠圖。最上方的線代表PublishSubject。第二條和第三條代表訂閱者。向上的虛線箭頭代表訂閱,向下的虛線箭頭代表發(fā)射事件。

- 第一個訂閱者在
1)之后,只能接收到2)和3)。 - 第二個訂閱者在
2)之后,只能接收到3)。
創(chuàng)建PublishSubject
創(chuàng)建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()
}
- 創(chuàng)建一個包含字符串類型的
PublishSubject。 - 向
subject中添加一個字符串,此時并沒有任何值輸出因為沒有訂閱。 - 為
subject創(chuàng)建一個訂閱,用于打印.next事件。但是此時并沒有打印。 - 用于銷毀回收。
由于PublishSubject只能發(fā)射在訂閱之后添加的事件,所以上面并沒有輸出結果。在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)
}
這種方式訂閱逃逸閉包參數(shù)是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已經(jīng)結束。事實上,任何類型的subject一旦終止,將不會再發(fā)射事件。
BehaviorSubjects
BehaviorSubjects工作方式類似于PublishSubjects,不同點是它會重新發(fā)射最晚的.next事件給新添加的訂閱者。
對應的圓珠圖如下:

- 第一條線是當前的
subject。 - 第一個訂閱者,在
①之后添加由于BehaviorSubjects可以重新發(fā)射最晚的一個元素給新的訂閱者,所以接收到事件序列是① ② ③。 - 同理第二個訂閱,可以接收的事件序列
② ③。
// 錯誤枚舉
enum LEError: Error {
case anError
}
// 錯誤枚舉
enum LEError: Error {
case anError
}
// 封裝打印方法
func print<T: CustomStringConvertible>(label: String, event: Event<T>) {
print(label, (event.element ?? event.error) ?? event)
}
example(of: "BehaviorSubject") {
// 1. 創(chuàng)建 subject
let subject = BehaviorSubject(value: "Initial Value")
let disposeBag = DisposeBag()
subject.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag) // 此時控制臺輸出 Initial Value
// 2. 發(fā)送一個事件
subject.onNext("X") // 輸出 X
// 3. 發(fā)送 Error事件
subject.onError(LEError.anError) // 輸出 anError
// 4. 終止后不會在輸出
subject.onNext("Y")
// 此時再添加訂閱 會輸出什么 ?
subject.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag) // 輸出 2) anError
// 5. 再添加事件,并不會被輸出。==> 終止后不會再發(fā)送事件
subject.onNext("Z")
}
- 創(chuàng)建
subject和disposeBag。 - 添加第一個訂閱,由于
BehaviorSubject的特性會攜帶上次的事件,此時會輸出初始1) Initial Value。 - 添加新的元素
X,訂閱者會將上一次事件和本次事件同時輸出。 - 兩種結束
subject的方式,當出現(xiàn)錯誤時終止或者發(fā)送complete時終止。發(fā)送onError使用錯誤終止subject,終止后就不會再發(fā)送新的事件。此時添加Y事件訂閱并沒有輸出信息。 - 添加新的訂閱,也不能接收
Y事件,此時Subject只會告訴訂閱已經(jīng)終止,因onError事件終止。
ReplaySubjects
ReplaySubject存在一個臨時的緩存,會重復將緩存中的元素發(fā)射給新的訂閱。對應的圓珠圖如下:

第一個訂閱者(中間線),第二個訂閱(下方線)。兩者的輸出結果都是序列① ② ③。
示例代碼如下:
example(of: "ReplaySubject") {
// 創(chuàng)建Subject,緩存大小為2。每次會將最晚的兩個發(fā)送給新的訂閱
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的緩存是保存在內(nèi)存中的,所以在使用的時候需要注意大小問題,防止浪費過的的內(nèi)存。盡量避免創(chuàng)建過多的緩存或存放大的對象。
PublishRelay & BehaviorRelay
PublishRelay用來包裝 PublishSubject;BehaviorRelay用來包裝 BehaviorSubject ,被包裝的 Subject 不會終止。通過 accept添加值而不是onNext,由于不會終止也就沒有 .error 和 .completed 事件。
下面看關于 PublishRelay 的例子。
example(of: "PublishRelay") {
// 1. 創(chuàng)建 relay
let relay = PublishRelay<String>()
let disposeBag = DisposeBag()
// 2. 添加一個元素
relay.accept("hello")
// 3. 添加訂閱,PublishRelay 是對 PublishSubject 的封裝具有其特性,并不會輸出 hello
relay.subscribe(onNext: {
print($0)
})
.disposed(by: disposeBag)
// 4. 添加元素,
relay.accept("1") // 輸出: 1
// 5. 如果發(fā)送 .completed 或者 .error 將會怎樣?
// relay.accept(LEError.anError) // 編譯器報錯。
}
PublishReplay是對PublishSubject的封裝具有其特性。
example(of: "BehaviorRelay") {
// 1. 定義
let relay = BehaviorRelay(value: "initial Value")
let disposeBag = DisposeBag()
// 2. 添加value
relay.accept("New Initial Value")
// 3. 訂閱
relay.subscribe {
print(label: "1)", event: $0)
}
.disposed(by: disposeBag)
// 3. 新Value
relay.accept("1") // 同 BehaviorSubject
// 4. 新的訂閱
relay.subscribe {
print(label: "2)", event: $0)
}
.disposed(by: disposeBag)
// 5. 新Value
relay.accept("2")
// 6. 直接通過 replay 獲取值。
print(relay.value)
}
Variables
在之前有提到,Variable是對BehaviorSubject的包裝,它會存儲當前的值作為狀態(tài)??梢酝ㄟ^value屬性訪問當前的值,也可以通過它修改值。通過value屬性可以代替onNext(_:)。
Variable包含BehaviorSubject的所有功能,存在初始值,可以重復發(fā)射最晚元素給新的訂閱。不過,想訪問Variable中包裝的BehaviorSubject需要使用asObservable()方法。
Variable與其他subject不同點,第一,不能向Variable中添加.error事件但是可以監(jiān)聽。在銷毀時自動發(fā)送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
小結
本節(jié)學習了Subjects概念及創(chuàng)建方法。理解基礎概念才能為以后的熟練運用打好基礎。