前言
RxSwift中Subject
是一種非常特殊的序列協(xié)議握爷,全局搜索class Subject / struct Subject都無結(jié)果肃拜,搜索protocol Subject獲取到SubjectType
協(xié)議淘正,其中有asObserver
方法璧亚,并且遵循ObservableType ->遵循->ObservableConvertibleType, 也就有了asObservable
方法结闸。
所以遵循SubjectType的序列同時(shí)有了序列和觀察者相應(yīng)的技能辩诞。在實(shí)際開發(fā)中應(yīng)用很頻繁坎弯,這樣就能自己訂閱自己,自己響應(yīng)自己的訂閱了译暂。
下面我們就來使用及分析幾種常用的Subject抠忘。
1、PublishSubject
先來看一段使用的示例代碼:
// 1:初始化序列
let publishSub = PublishSubject<Int>() //初始化一個(gè)PublishSubject 裝著Int類型的序列
// 2:發(fā)送響應(yīng)序列
publishSub.onNext(1)
// 3:訂閱序列
publishSub.subscribe { print("訂閱到了:", $0) }
.disposed(by: disposbag)
// 再次發(fā)送響應(yīng)
publishSub.onNext(2)
publishSub.onNext(3)
//輸出:訂閱到了: next(2)
//訂閱到了: next(3)
查看輸出外永,只打印了2和3崎脉,那為什么1沒有打印呢?
上一篇文章RxSwift - 高階函數(shù)中關(guān)于publish
的使用分析伯顶,其中publish就是對(duì)PublishSubject的封裝囚灼,證明了publish就是將多個(gè)觀察者交由唯一的PublishSubject來管理,PublishSubject將所有觀察者.on
方法保存祭衩,并順序地響應(yīng)外界的訂閱事件灶体。
那么此處關(guān)于1為響應(yīng)的問題,還是得分析訂閱.subscribe
及PublishSubject.on
關(guān)鍵代碼:
此處.onNext(1)執(zhí)行時(shí)掐暮,暫未有過
.subscribe
訂閱蝎抽,所以暫不會(huì)生成任何觀察者,即PublishSubject中的observers中啥也沒有路克,也就不會(huì)有任何的observer.on響應(yīng)任何的事件织中。那么.onNext(1)也就無從響應(yīng)锥涕。當(dāng)
publishSub.subscribe
之后,才有了觀察者狭吼,observers中也就會(huì)將觀察者.on保存以響應(yīng)后續(xù)事件。
2殖妇、BehaviorSubject
let behaviorSub = BehaviorSubject.init(value: 100)
// 2:發(fā)送信號(hào)
//behaviorSub.onNext(2)
//behaviorSub.onNext(3)
// 3:訂閱序列
behaviorSub.subscribe{ print("訂閱到了1:", $0) }
.disposed(by: disposbag)
// 再次發(fā)送
//behaviorSub.onNext(4)
//behaviorSub.onNext(5)
// 再次訂閱
behaviorSub.subscribe{ print("訂閱到了:", $0) }
.disposed(by: disposbag)
//輸出:訂閱到了1: next(100)
//訂閱到了: next(100)
此時(shí)代碼中的2刁笙、3、4谦趣、5都是已注釋狀態(tài)疲吸,那么訂閱之后會(huì)響應(yīng)初始值100。
打開2前鹅、3的注釋結(jié)果打诱病:
訂閱到了1: next(3)
訂閱到了: next(3)
說明此時(shí)響應(yīng)了最新的序列元素3。
接著打開4舰绘、5的注釋結(jié)果打吁逵鳌:
訂閱到了1: next(3)
訂閱到了1: next(4)
訂閱到了1: next(5)
訂閱到了: next(5)
此時(shí)可以理解為,在第一次訂閱之前的只響應(yīng)最新
的捂寿,但訂閱之后的都會(huì)響應(yīng)口四,第二次訂閱響應(yīng)了之前最新的序列5。
問題一
:為什么會(huì)有初始值秦陋,且會(huì)保存最新的值蔓彩?
問題二
:相對(duì)于PublishSubject沒有訂閱就無法響應(yīng)的問題,BehaviorSubject怎么能響應(yīng)訂閱之前的事件驳概?
查看BehaviorSubject初始化方法:
發(fā)現(xiàn)利用一個(gè)屬性變量
element
將初始化的值保存起來赤嚼。那這個(gè)element是怎么保存最新值的?每當(dāng).onNext()時(shí)候就會(huì)來到BehaviorSubject.on方法中顺又,接著在synchronized_on中將最新值給element:
接著我們來看訂閱之后都干了什么:
與PublishSubject不同的是更卒,訂閱發(fā)起時(shí)候,在保存完observer.on之后待榔,默認(rèn)會(huì)執(zhí)行一次observer.on即會(huì)響應(yīng)之前保存最新的序列元素值逞壁。每一次.onNext的作用就是
更新element
的值。
3锐锣、ReplaySubject
let replaySub = ReplaySubject<Int>.create(bufferSize: 2)
// let replaySub = ReplaySubject<Int>.createUnbounded()
// 2:發(fā)送信號(hào)
replaySub.onNext(1)
replaySub.onNext(2)
replaySub.onNext(3)
replaySub.onNext(4)
// 3:訂閱序列
replaySub.subscribe{ print("訂閱到了:", $0) }
.disposed(by: disposbag)
// 再次發(fā)送
replaySub.onNext(7)
replaySub.onNext(8)
replaySub.onNext(9)
//輸出:訂閱到了: next(3)
//訂閱到了: next(4)
//訂閱到了: next(7)
//訂閱到了: next(8)
//訂閱到了: next(9)
能打印3腌闯、4以及對(duì)應(yīng)bufferSize: 2
可知,會(huì)保存下最新的有效的兩次序列元素雕憔。
我們直接來到.create方法:
其中還對(duì)緩存?zhèn)€數(shù)是否為1做了區(qū)分姿骏。ReplayOne與ReplayMany有共同的祖先類
ReplayBufferBase
,所以我們來到ReplayBufferBase:所以查看訂閱以及響應(yīng)可直接看ReplayBufferBase中的實(shí)現(xiàn)方法斤彼。重點(diǎn)看ReplayBufferBase中的
on
和subscribe
兩個(gè)方法實(shí)現(xiàn):addValueToBuffer
字面意思就是將值存入緩存分瘦,重點(diǎn)U盒骸!嘲玫!分別查看在子類ReplayOne和ReplayManyBase中的實(shí)現(xiàn):
override func addValueToBuffer(_ value: Element) {
self.value = value
}
override func addValueToBuffer(_ value: Element) {
self.queue.enqueue(value)
}
ReplayOne只是將當(dāng)前值保存在屬性self.value
中悦施,當(dāng)訂閱發(fā)起時(shí)就會(huì)調(diào)用.replayBuffer
方法,便會(huì)執(zhí)行當(dāng)先觀察者.on觸發(fā)事件去团,并將當(dāng)前保存的value發(fā)射出去:
ReplayManyBase則是將值保存在值保存在一個(gè)集合
屬性中抡诞,通過添加及更新集合中的元素來達(dá)到只緩存相應(yīng)個(gè)數(shù)的元素。當(dāng)訂閱發(fā)起時(shí)土陪,就會(huì)從集合中將觀察者一個(gè)一個(gè)拿出來觸發(fā).on
方法昼汗。
緩存機(jī)制分析
當(dāng)訂閱發(fā)起之前,ReplayManyBase將值保存到集合中鬼雀,集合中的
queue
容量大小是緩存大小加1
顷窒,訂閱前一開始能存下1、2源哩、3鞋吉,此時(shí)會(huì)觸發(fā)trim()
,即會(huì)調(diào)用刪除queue中元素queue為nil璧疗、2坯辩、3,當(dāng)要存4時(shí)候崩侠,按照替換index為0->1->2->0這樣的循環(huán)順序?qū)⒑罄m(xù)數(shù)值更新
進(jìn)queue中漆魔,所以此時(shí)的queue由是4、2却音、3改抡,此時(shí)也會(huì)觸發(fā)trim(),刪除舊的值2系瓢,queue此時(shí)為4阿纤、nil、3夷陋,此時(shí)訂閱發(fā)起欠拾,執(zhí)行self.replayBuffer(anyObserver),所以會(huì)遍歷queue中的item用于響應(yīng)事件骗绕,斷點(diǎn)調(diào)試為逆序遍歷即先發(fā)射3再發(fā)射4藐窄。當(dāng)后續(xù)7來到時(shí)直接走的.on方法,當(dāng)7來時(shí)酬土,此時(shí)queue為4荆忍、7、3,會(huì)直接走
dispatch(self.synchronized_on(event), event)
即:
接著會(huì)queue為4刹枉、7叽唱、nil,然后發(fā)射當(dāng)前的7出去微宝。
當(dāng)8來時(shí)棺亭,queue為4、7芥吟、8侦铜,然后調(diào)用dequeue()后queue為nil、7钟鸵、8,也只會(huì)響應(yīng)oberver.on(event)涤躲,那么也只會(huì)發(fā)射8出去棺耍。
當(dāng)9來時(shí),queue為9种樱、7蒙袍、8,然后調(diào)用dequeue()嫩挤,最后queue為9害幅、nil、8岂昭。
所以queue變化為:
( 1, 2, 3) -> ( nil, 2, 3) -> ( 4, 2, 3) -> ( 4, nil, 3) -> ( 4, 7, 3) -> ( 4, 7, nil) -> ( 4, 7, 8) -> ( nil, 7, 8) -> ( 9, 7, 8) -> ( 9, nil, 8)
發(fā)射的順序還是按響應(yīng)的先后順序發(fā)射以现。
4、AsyncSubject
// 1:創(chuàng)建序列
let asynSub = AsyncSubject<Int>.init()
// 2:發(fā)送信號(hào)
asynSub.onNext(1)
asynSub.onNext(2)
// 3:訂閱序列
asynSub.subscribe{ print("訂閱到了:", $0) }
.disposed(by: disposbag)
// 再次發(fā)送
asynSub.onNext(3)
asynSub.onNext(4)
asynSub.onError(NSError.init(domain: "LcrError", code: 10085, userInfo: nil))
asynSub.onCompleted()
//輸出:訂閱到了: error(Error Domain=LcrError Code=10085 "(null)")
發(fā)現(xiàn)當(dāng)有錯(cuò)誤響應(yīng)時(shí)约啊,前面的響應(yīng)都無效了邑遏。
將錯(cuò)誤注釋,再次打印結(jié)果為:
訂閱到了: next(4)
訂閱到了: completed
然后再給onCompleted
添加注釋恰矩,發(fā)現(xiàn)沒有打印任何東西记盒。
里面的工作原理是怎樣的呢?
我們把目光聚集在.on
方法上:
當(dāng)
.onNext
來時(shí)外傅,synchronized_on
中會(huì)將當(dāng)前值設(shè)為最新值纪吮,意思就是只保留最新的值,然后判斷.completed有沒有來萎胰。沒有的話就返回(Observers(),.completed)
到.on中碾盟,即此時(shí)沒有有效觀察者,即使dispatch進(jìn)去了也啥也不做奥洼。當(dāng)
.onError
來時(shí)巷疼,會(huì)將前面保存的觀察者全部刪除
,即使回到.on中也只會(huì)發(fā)射錯(cuò)誤信息。當(dāng)
.completed
來時(shí)嚼沿,會(huì)將保存的最新的值返回放入.next返回到.on去的.next:
中去執(zhí)行估盘,最終也會(huì)調(diào)用.completed。
所以一定需要有.completed來且不能發(fā)射錯(cuò)誤骡尽,才能正常響應(yīng)訂閱信息遣妥。
5、BehaviorRelay
Variable : 5.0已經(jīng)廢棄(BehaviorRelay 替換)
// 1:創(chuàng)建序列
let variableSub = BehaviorRelay.init(value: 1)
// 2:發(fā)送信號(hào)
variableSub.accept(100)
variableSub.accept(10)
// 3:訂閱信號(hào)
variableSub.asObservable().subscribe{ print("訂閱到了:", $0) }
.disposed(by: disposbag)
print("打印:\(variableSub.value)")
// 再次發(fā)送
variableSub.accept(1000)
//輸出:訂閱到了: next(10)
//打印:10
//訂閱到了: next(1000)
BehaviorRelay不僅可以緩存最新的元素攀细,還可以通過屬性訪問到最新緩存的元素箫踩。
源碼可知,BehaviorRelay就是內(nèi)部封裝了一個(gè)BehaviorSubject類型的序列subject谭贪,利用BehaviorSubject能緩存以及能訪問屬性value的特性來實(shí)現(xiàn)屬性訪問境钟。