iOS Reactivecocoa(RAC)知其所以然(源碼分析,一篇足以)

前言

如今RAC大行其道,對其講解的博客也多不勝數,稍微有點經驗的估計也已經對這個爽到不要不要的框架運用自如了,真正沉下來研究其實現原理的估計也不在少數,這里僅僅是記錄一下自己的分析理解,更是在寫這篇博客的過程中深化自己對RAC的認知,可能就是想到哪寫到哪,各位朋友能從其中學到東西是最好了,要是感覺沒什么干貨也別對小弟拍磚啊!

一、關于常見類

1、RACSiganl 信號類的使用

如下圖:

信號類使用圖

完成一個信號的生命周期分為四步:

  • 1、創建信號
  • 2、訂閱信號
  • 3、發送信號
  • 4、取消訂閱(圖中未標明)

下面每一步我們細細道來:

1、創建信號

由上面的 信號類使用圖可知,創建信號類方法中傳入了一個返回值是RACDisposable 類型,參數是遵守 RACSubscriber 協議的吧,名為 didSubscribe 的block,具體實現如下:

由上圖可知,內部創建的是一個 RACDynamicSignal 類型的信號,并將 didSubscribe 傳入,內部實現如下:

這里就是重點了,首先先創建了一個 RACDynamicSignal 類型的信號,然后將傳入的名為 didSubscribe 的block保存在創建的信號的 didSubscribe 屬性中,此時僅僅是保存并未觸發

一句話總結:創建信號本質就是創建了一個 RACDynamicSignal 類型的信號,并將傳入的代碼塊保存起來,留待以后調用。

2、訂閱信號

由上面的 信號類使用圖可知,有三種訂閱信號的方式,分別是訂閱 NextErrorcompleted ,內部實現如下:

首先我們來看第一步,就是創建一個訂閱者,并傳入相應的block,創建實現如下:

很明顯,創建訂閱者的實質是,創建一個訂閱者,并保存相應的block,比如 neterror、或者complete此時僅僅是保存并未觸發!

由上面兩圖可知,三種訂閱方式的流程模式是一致的,僅僅是保存的block不同而已,我們分析一種即可,so 接下來就以 subscribeNext 為例來逐步分析。

接下來,我們看看執行訂閱命令這塊的實現,如下:

這里,首先我們要知道此處代碼實現是在 RACDynamicSignal 里,圖中的 didSubscribe 就是第一步創建信號中保存的 didSubscribe block。

由上圖可知第一步創建信號中保存的 didSubscribe 代碼塊在這里執行,并傳入了剛剛生成的訂閱者(此處的訂閱者中保存里 Next block代碼塊)。

額外的,這里生成了一個 RACCompoundDisposable 類型的disposable,用來管理整個訂閱結束及資源的清理,并以傳入的訂閱者、及當前信號、剛創建的disposable 生成一個 RACPassthroughSubscriber 類型的訂閱者,此訂閱者僅僅是將傳入的三個對象整體包裝了一下而已,實質起作用的還是在剛才創建的訂閱者,所以,其包含的 next 代碼塊,依然直接調用即可。

之后,將執行 didSubscribe 代碼塊返回的 innerDisposable 傳入剛剛生成disposable、并將執行此代碼塊的信號的 schedulingDisposable 也保存到RACCompoundDisposable 類型的disposable中,然后統一管理整個訂閱結束及資源的清理。

其中的innerDisposable 就是 上面的 信號類使用圖 中表示的第四步,其會在信號結束訂閱的時候被調用,做一些清理資源的工作。

而 schedulingDisposable 的話,實際上就是執行代碼塊放到相應的調度器中,假如沒有設置的話,即為 backgroundScheduler,并異步執行 didSubscribe 代碼塊,假如設置的話就會返回nil,并直接執行代碼塊,此處嵌套就有點深了,隨后講解取消訂閱的時候再細說。

此處用到的RACCompoundDisposable 類型的disposable有點類似于可變數組 NSMutableArray 。而當 RACCompoundDisposable 對象被 disposed 時,它會調用其所包含的所有 disposable 對象的 -dispose 方法。所以可以做統一管理。

一句話總結:訂閱信號本質就是創建了一個 RACPassthroughSubscriber 類型的訂閱者,并將傳入的代碼塊保存起來,留待以后調用,同時調用了第一步創建信號中保存的代碼塊,并傳入創建的訂閱者。

3、發送信號

由上面的 信號類使用圖可知,發送同樣對應著三種方式,處理如下:

這里有三點需要知道

第一,發送信號就是執行相應block,此處執行的就是第二步中保存的相應的block。

第二,對于 sendErrorsendCompleted 都是先取消訂閱,再執行相應的代碼塊,而 sendNext 并未使訂閱結束,這樣的話,對之后討論的各種組合方法中必須寫上 sendCompleted來結束訂閱的做法就好理解了。

第三,我們也看到三種方法中,假如信號沒有相應的block代碼塊保存,即沒有經過第二步去訂閱保存代碼塊,就算是發送了信號也不會執行,此時也就是冷熱信號的區別,當然用 RACSubject 來解釋更容易理解。

一句話總結:發送信號就是執行訂閱信號時對應的block。

4、取消訂閱

經過上面三步,我們也了解到了想要結束訂閱只要將相應生成的disposable執行dispose即可,那到底為什么呢?現在來講一講:

第二步訂閱信號中有講到,執行訂閱的代碼塊實際上是放到相應的調度器中去執行的,如下圖:

如上圖,是不是很有疑問,假如currentScheduler不為nil的話,那豈不是沒有dispose啥事,代碼塊就直接執行了?我當時也納悶,不過框架中敘述如下(此處感謝雷神指導,么么噠!):

此時:代碼塊就不會放到調度器里去執行,而是直接執行,此時disposable的dispose方法就沒啥卵用了。

假如currentScheduler為nil的話,會默認一個調度器去管理代碼塊,具體實現如下:

如圖所示,可知在代碼塊未被調度的之前,生成的disposable被dispose的話,代碼塊就不會被執行。

一句話總結:取消訂閱就是把訂閱信號獲得的disposable 進行dispose即可在調度器調度該部分代碼之前禁止調用。

2、RACSubject 的使用

RACSignal的原理搞清了,接下來就比較好理解了,畢竟信號類都是繼承與RACSignal,作用不同也僅僅是部分實現的方式不同而已。

如下圖:

1、創建信號

由上圖可知,創建RACSubject對象的時候同時創建了相應的一個disposable和一個訂閱者數組,沒有做其他事情。

一句話總結:創建信號就是額外實例化了一個訂閱者數組,沒有做其他事情。

2、訂閱信號

大致流程與RACSignal是一致的,不同點在下面:

RACSinal訂閱的時候直接執行了訂閱命令,執行的是創建時保存的代碼塊,但RACSubject的做法是將訂閱者保存到初始化時生成的那個訂閱者數組內,此時每個訂閱者都包含其對應的代碼塊(比如:next、error、complete)。

由此可以知道,RACSubject是可以多次訂閱的,多次訂閱就是把相應包含代碼塊的訂閱者放入訂閱者數組內。

另外,此處對于取消訂閱做了一些清理工作,將保存的訂閱者都移除。

一句話總結:訂閱信號就是將代碼塊保存到訂閱者中,并將訂閱者添加到信號的訂閱者數組中。

3、發送信號

大致流程與RACSignal是一致的,不同點在下面:

由上圖可知,RACSubject的發送信號就是遍歷自己的訂閱者數組,然后分別發送信號。

這也是對月RACsubjec為什么假如有多個訂閱者,發送一個信號所有的訂閱者都收到的原因。

一句話總結:發送信號就是遍歷自己的訂閱者數組,然后分別發送信號,并執行該訂閱者保存的代碼塊。

3、其他信號類

還是那句話,信號類都是繼承與RACSignal,作用不同也僅僅是部分實現的方式不同而已。

如不同的訂閱信號實現:

不同的發送信號實現:

授人以魚不如授人以漁,知道了這種分析流程方法,其他相應的分析類似,朋友們可以自己試試分析一下,這樣你就會發現其實并不是那么難!

4、RACCommand 的使用

這個就比較復雜了,因為他是基于信號的一個對象,里面綁定了很多相關的信號,這里只是講一講他的執行原理,及常用的一些方法的分析。

先上圖如下:

RACCommand流程圖

1、創建命令

由上面 RACCommand流程圖 可知創建的時候傳入一個返回值為RACSignal類型,參數為id類型的名為input的一個block,具體實現如下:

由上圖可知,command的創建實質是額外創建了一個名為 _activeExecutionSignals 的信號數組,并把傳入的block保存為 signalBlock 屬性。

初始化的時候實際上還有很多其他綁定的代碼,就不一一細說了,用到的下面會講一下。

一句話總結:創建命令就是創建了一個RACCommand類的對象,并將傳入的block保存為 signalBlock 屬性,然后初始化了一個信號數組,留待以后接收命令信號。

2、訂閱命令發出的信號

由上面 RACCommand流程圖 可知訂閱的信號經過了兩步方法,第一步中executionSignals為創建命令時綁定的信號如下:

由上圖可知,executionSignals實際上就是最新的第一步創建命令中創建的activeExecutionSignals信號數組。

第二步中的switchToLatest實質上取信號數組的最新的信號。

然后就訂閱信號,月RACSignal的訂閱流程一樣,不再贅述。

一句話總結:訂閱命令實質上就是訂閱命令保存的信號數組中的最新的信號(目前代碼的訂閱)

3、判斷命令是否執行

與executionSignals類似,在創建命令時也創建了一個信號來監聽當前命令是否正在執行。

如上圖所示,executing信號實際上是在檢測是否有活躍信號,replayLast能確保是最新的值。

一句話總結:判斷是否執行命令就是檢測是否有活躍信號

4、執行命令

執行命令的話就是傳入你所需要傳入的對象即可,如下圖

由上圖,我們可以知道,執行命令先執行了第一步保存的signalBlock并傳入接收到的參數,然后把得到的signal加入到第一步創建的 _activeExecutionSignals 信號數組中,留待第二步訂閱使用。

一句話總結:執行命令就是將傳入的對象傳入signalBlock生成signal,并將signal添加到_activeExecutionSignals 信號數組中。

二、關于常用操作方法

1、核心方法bind

在實際開發中我們并不需要直接調用bind方法,但我們所使用API都是以bind方法為基礎的,想要了解操作方法的實現原理,bind我們不得不深究一下。

先上 bind 流程圖如下:

bind操作流程圖

由上圖可知,bind 操作方法的流程如下:

  • 1、創建信號源
  • 2、綁定源信號,生成綁定信號
  • 3、訂閱綁定信號
  • 4、發送源信號
  • 5、取消訂閱(不再贅述)

由此可知,與正常信號綁定訂閱流程不同之處在于,多了一個綁定過程,并且最終訂閱的是綁定生成的綁定信號。接下來我們一個一個理一下思路:

1、創建信號源

一句話總結:創建了一個 RACDynamicSignal 類型的信號,并將傳入的代碼塊保存起來,留待以后調用。

2、綁定源信號,生成綁定信號

bind操作流程圖可知,bind 方法傳入了一個 RACStream * (^RACStreamBindBlock)(id value, BOOL *stop) 的block代碼塊,其具體實現如下(只截取核心的代碼):

由上圖可知,bind操作實際上直接返回了一個綁定信號信號,并將 didSubscriber 代碼塊 傳入,保存到返回的信號內。

額外的,綁定信號的 didSubscriber 代碼塊 中做了兩件事

第一,將bind方法傳入的block保存為bindinfBlock,留待以后用,并且初始化了一個信號數組。

第二,在代碼塊內部訂閱了源信號,并做了一些處理,如下:

由上面可知,訂閱源信號傳入的block,被保存在相應訂閱者中,留待源信號發送信號觸發,內部實現不再贅述。

一句話總結:bind操作實際上是直接生成綁定信號并返回,并且在生成綁定信號傳入的didSubscriber block代碼塊中,保存了bind傳入的block,初始化了信號數組,并且訂閱了源信號,針對源信號發送信號的流程做了一些處理。(此時未執行,訂閱才執行)

3、訂閱綁定信號

訂閱綁定信號就是實現了第二步中的 didSubscriber 代碼塊,就是說,第二步僅僅是保存,在訂閱它的時候才會實現。

一句話總結:訂閱綁定信號就是保存了nextBlock,并且創建訂閱者,實現信號的didSubscriber block代碼塊。

4、發送信號

因為bind比較復雜,所以在此可以將其串起來

1、發送信號觸發綁定信號 didSubscriber 代碼塊中訂閱源信號時傳入的NextBlock 代碼塊,如下:

2、是執行第三步中訂閱綁定信號保存的從bind傳入的額block代碼塊,并傳入由訂閱時的傳參生成的新的傳參,生成returnSignal,具體實現如下:

3、returnSignal內部實現如下

由代碼可知,returnSignal實際上是保存了傳入的新值,此時就是真正意義上的將源輸入做了改變,最后實際輸出的就是新保存的值。

4、接下來就是將生成的returnSignal做 addSignal 操作如下:

由上圖代碼可知,內部實現是直接訂閱了傳入的returnSignal。

5、之前我們也說過,不同信號對應不同的訂閱代碼,所以我們來看看returnSignal的訂閱時的代碼,如下:

由上圖代碼可知,returnSignal在訂閱的時候就同時做了發送信號的任務,即直接就會走訂閱保存的代碼塊,如下:

此處觸發的是綁定信號的訂閱者發送信號,就是執行綁定信號的 nextBlock,即:

此時就有了運行結果:

ok,bind操作方法就到這了,我已經用盡了我的洪荒之力,么么噠!

一句話總結:bind操作方法實質上就是生成新的綁定信號,利用returnSignal作為中間信號來改變源數據生成新的數據并執行新綁定信號的nextBlock代碼塊!

2、映射方法 map

所有操作方法都是以bind方法為核心的,這里分析一下映射方法即可,不在贅述其他方法,原理都是一樣的,就是看怎么組合,怎么去處理相關邏輯達到不同操作方法達成的效果!

首先,看一下map的操作流程圖,如下:

很顯然,與 bind 的區別就是生成綁定信號的過程不同,具體實現如下:

由上圖可知,從外面傳入了一個參數為value,返回值為id類型的block代碼塊,時機返回的是 flattenMap,操作方法生成的信號,具體實現如下:

看,就是這里,在 map 方法中,返回的是 flattenMap 方法生成的信號,而 flattenMap 內部返回的是 bind 方法返回的信號,又回到了第一部分講解的 bind實現原理

上圖中的 stream,就是returnSignal,即 flattenMap方法傳入的 block(value),如下圖:

具體生成ReturnSignal的方法,還得往回看 map 方法如下:

由上圖可知,傳入的value已經被 map保存的方法所映射(即改變),如下:

一句話總結:map 映射方法的實質就是 bind 方法的深度封裝,實際的信號流的流程還是以 bind為核心,只是巧妙的做了一些邏輯處理而已。

3、其他操作方法

對于操作方法,核心就是 bind,各種作用的不同就是各種 signalbind 方法的結合,外加一些巧妙的邏輯處理,明白了信號的原理、綁定的原理,任何其他的組合或者運用都是萬變不離其宗的,寫更多也不如自己去研究一下!么么噠!

所以,操作方法就寫到這里!

四、后記

額,這樣就差不多了吧,弄明白了本篇所介紹的幾個常用的最基本的原理,其實已經對RAC了解的七七八八了,當然RAC還有很多值得去學習的思想,比如RAC宏的使用各種UI操作的處理,他們其中的設計與原理也都很巧妙,網上也有很多博客介紹了,這里就暫時不深入探討了,畢竟寫到這里篇幅就已經夠長了,大家有興趣可以自己去看看,以后有時間了我在整理一下!

本文由作者 王隆帥 編寫,轉載請保留版權網址,感謝您的理解與分享,讓生活變的更美好!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,316評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,481評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,241評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,939評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,697評論 6 409
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,182評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,247評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,406評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,933評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,772評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,973評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,516評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,638評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,866評論 1 285
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,644評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,953評論 2 373

推薦閱讀更多精彩內容

  • RAC使用測試Demo下載:github.com/FuWees/WPRACTestDemo 1.ReactiveC...
    FuWees閱讀 6,430評論 3 10
  • Spring Cloud為開發人員提供了快速構建分布式系統中一些常見模式的工具(例如配置管理,服務發現,斷路器,智...
    卡卡羅2017閱讀 134,781評論 18 139
  • 1.ReactiveCocoa常見操作方法介紹。 1.1 ReactiveCocoa操作須知 所有的信號(RACS...
    萌芽的冬天閱讀 1,032評論 0 5
  • 前言由于時間的問題,暫且只更新這么多了,后續還會持續更新本文《最快讓你上手ReactiveCocoa之進階篇》,目...
    Karos_凱閱讀 1,749評論 0 6
  • 山林伴我度童年,我伴山林扛上肩。親熱之余贏小錢。看昨天,往事如煙飛過川。 又 岸邊楊柳問桃林,河里青蛙出水吟...
    木貞ma閱讀 900評論 1 3