RxSwift - Suject

前言

RxSwift中Subject是一種非常特殊的序列協(xié)議握爷,全局搜索class Subject / struct Subject都無結(jié)果肃拜,搜索protocol Subject獲取到SubjectType協(xié)議淘正,其中有asObserver方法璧亚,并且遵循ObservableType ->遵循->ObservableConvertibleType, 也就有了asObservable方法结闸。

SujectType

所以遵循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)的問題,還是得分析訂閱.subscribePublishSubject.on關(guān)鍵代碼:

subscribe

PublishSubject.on

此處.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初始化方法:

BehaviorSubject初始化

發(fā)現(xiàn)利用一個(gè)屬性變量element將初始化的值保存起來赤嚼。
那這個(gè)element是怎么保存最新值的?每當(dāng).onNext()時(shí)候就會(huì)來到BehaviorSubject.on方法中顺又,接著在synchronized_on中將最新值給element:
element賦最新值

接著我們來看訂閱之后都干了什么:

.subscribe

與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方法:

.create

其中還對(duì)緩存?zhèn)€數(shù)是否為1做了區(qū)分姿骏。ReplayOne與ReplayMany有共同的祖先類ReplayBufferBase,所以我們來到ReplayBufferBase:
ReplayBufferBase

所以查看訂閱以及響應(yīng)可直接看ReplayBufferBase中的實(shí)現(xiàn)方法斤彼。重點(diǎn)看ReplayBufferBase中的onsubscribe兩個(gè)方法實(shí)現(xiàn):
synchronized_on

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ā)射出去:

ReplayOne

ReplayManyBase則是將值保存在值保存在一個(gè)集合屬性中抡诞,通過添加及更新集合中的元素來達(dá)到只緩存相應(yīng)個(gè)數(shù)的元素。當(dāng)訂閱發(fā)起時(shí)土陪,就會(huì)從集合中將觀察者一個(gè)一個(gè)拿出來觸發(fā).on方法昼汗。

ReplayManyBase.replayBuffer

緩存機(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)即:

dispatch

接著會(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方法上:

.on

synchronized_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

源碼可知,BehaviorRelay就是內(nèi)部封裝了一個(gè)BehaviorSubject類型的序列subject谭贪,利用BehaviorSubject能緩存以及能訪問屬性value的特性來實(shí)現(xiàn)屬性訪問境钟。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市俭识,隨后出現(xiàn)的幾起案子慨削,更是在濱河造成了極大的恐慌,老刑警劉巖套媚,帶你破解...
    沈念sama閱讀 211,376評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件缚态,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡堤瘤,警方通過查閱死者的電腦和手機(jī)玫芦,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來本辐,“玉大人桥帆,你說我怎么就攤上這事∈χ#” “怎么了环葵?”我有些...
    開封第一講書人閱讀 156,966評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)宝冕。 經(jīng)常有香客問我张遭,道長(zhǎng),這世上最難降的妖魔是什么地梨? 我笑而不...
    開封第一講書人閱讀 56,432評(píng)論 1 283
  • 正文 為了忘掉前任菊卷,我火速辦了婚禮,結(jié)果婚禮上宝剖,老公的妹妹穿的比我還像新娘洁闰。我一直安慰自己,他們只是感情好万细,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,519評(píng)論 6 385
  • 文/花漫 我一把揭開白布扑眉。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪腰素。 梳的紋絲不亂的頭發(fā)上聘裁,一...
    開封第一講書人閱讀 49,792評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音弓千,去河邊找鬼衡便。 笑死,一個(gè)胖子當(dāng)著我的面吹牛洋访,可吹牛的內(nèi)容都是我干的镣陕。 我是一名探鬼主播,決...
    沈念sama閱讀 38,933評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼姻政,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼呆抑!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起汁展,我...
    開封第一講書人閱讀 37,701評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤理肺,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后善镰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,143評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡年枕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,488評(píng)論 2 327
  • 正文 我和宋清朗相戀三年炫欺,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熏兄。...
    茶點(diǎn)故事閱讀 38,626評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡品洛,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出摩桶,到底是詐尸還是另有隱情桥状,我是刑警寧澤,帶...
    沈念sama閱讀 34,292評(píng)論 4 329
  • 正文 年R本政府宣布硝清,位于F島的核電站辅斟,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏芦拿。R本人自食惡果不足惜士飒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,896評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蔗崎。 院中可真熱鬧酵幕,春花似錦、人聲如沸缓苛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至笔刹,卻和暖如春芥备,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徘熔。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國(guó)打工门躯, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人酷师。 一個(gè)月前我還...
    沈念sama閱讀 46,324評(píng)論 2 360
  • 正文 我出身青樓讶凉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親山孔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子懂讯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,494評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容