RACSignal 的 Subscription 深入分析

發表於2015-10-22

ReactiveCocoa是一個FRP的思想在Objective-C中的實現框架,目前在美團的專案中被廣泛使用。對於ReactiveCocoa的基本用法,網上有很多相關的資料,本文不再討論。RACSignal是ReactiveCocoa中一個非常重要的概念,而本文主要關注RACSignal的實現原理。在閱讀之前,你需要基本掌握RACSignal的基本用法

本文主要包含2個部分,前半部分主要分析RACSignal的subscription過程,後半部分是對前半部分的深入,在subscription過程的基礎上分析ReactiveCocoa中比較難理解的兩個操作:multicast && replay。

PS:為了解釋清楚,我們下面只討論next,不討論error以及completed,這二者與next類似。本文基於ReactiveCocoa 2.x版本。
我們先刨析RACSignal的subscription過程

RACSignal的常見用法

Subscription過程概括

RACSignal的Subscription過程概括起來可以分為三個步驟:

  1. [RACSignal createSignal]來獲得signal
  2. [signal subscribeNext:]來獲得subscriber,然後進行subscription
  3. 進入didSubscribe,通過[subscriber sendNext:]來執行next block

步驟一:[RACSignal createSignal]來獲得signal

[RACSignal createSignal]會呼叫子類RACDynamicSignal的createSignal來返回一個signal,並在signal中儲存後面的 didSubscribe這個block

步驟二:[signal subscribeNext:]來獲得subscriber,然後進行subscription

  1. [signal subscribeNext]先會獲得一個subscriber,這個subscriber中儲存了nextBlock、errorBlock、completedBlock
  2. 由於這個signal其實是RACDynamicSignal型別的,這個[self subscribe]方法會呼叫步驟一中儲存的didSubscribe,引數就是1中的subscriber

步驟三:進入didSubscribe,通過[subscriber sendNext:]來執行next block

任何時候這個[subscriber sendNext:],就直接呼叫nextBlock

signal的subscription過程回顧

從上面的三個步驟,我們看出:

  • 先通過createSignal和subscribeNext這兩個呼叫,宣告了流中value到來時的處理方式
  • didSubscribe block塊中非同步處理完畢之後,subscriber進行sendNext,自動處理

搞清楚了RAC的subscription過程,接著在此基礎上我們討論一個RACSignal中比較容易混淆的兩個操作:multicast和replay。

為什麼要清楚這兩者的原理

  • 在RACSignal+Operation.h中,連續定義了5個跟我們這個主題有關的RACSignal的操作,這幾個操作的區別很細微,但用錯的話很容易出問題。只有理解了原理之後,才明白它們之間的細微區別
  • 很多時候我們意識不到需要用這些操作,這就可能因為side effects執行多次而導致程式bug

multicast && replay的應用場景

“Side effects occur for each subscription by default, but there are certain situations where side effects should only occur once – for example, a network request typically should not be repeated when a new subscriber is added.”

  1. 在上面的例子中,如果我們不用RACMulticastConnection的話,那就會因為執行了兩次subscription而導致發了兩次網路請求。
  2. 從上面的例子中,我們可以看到對一個Signal進行multicast之後,我們是對connection.signal進行subscription而不是原來的networkRequest。這點是”side effects should only occur once”的關鍵,我們將在後面解釋

multicast原理分析

replay是multicast的一個特殊case而已,而multicast的整個過程可以拆分成兩個步驟,下面進行詳細討論

multicast的機制Part 1:

  • 結合上面的例子來看,RACMulticastConnection的init是以networkRequest作為sourceSignal,而最終connnection.signal指的是[RACReplaySubject subject]

  • 結合上面的RACSignal分析的Subscription過程,[self.sourceSignal subscribe:_signal]會執行self.sourceSignal的didSubscribe這個block。再結合上面的例子,也就是說會把_signal作為subscriber,髮網路請求,success的時候,_signal會sendNext,這裡的這個signal就是[RACReplaySubject subject]。可以看出,一旦進入到這個didSubscribe中,後續的不管是sendNext還是subscription,都是對這個[RACReplaySubject subject]進行的,與原來的sourceSignal徹底無關了。這就解釋了為什麼”side effects only occur once”。

multicast的機制Part 2:

在進行multicast的步驟二之前,需要介紹一下RACSubject以及RACReplaySubject

———————惱人的分隔線 start——————

RACSubject

“A subject can be thought of as a signal that you can manually control by sending next, completed, and error.”

RACSubject的一個用法如下:

接下來分析RACSubject的原理

  • RACSubject中有一個subscribers陣列

  • 從subscribe:的實現可以看出,對RACSubject物件的每次subscription,都是將這個subscriber加到subscribers陣列中而已

  • 從sendNext:的實現可以看出,每次RACSubject物件sendNext,都會對其中保留的subscribers進行sendNext,如果這個subscriber是RACSignal的話,就會執行Signal的next block。

RACReplaySubject

“A replay subject saves the values it is sent (up to its defined capacity) and resends those to new subscribers.”,可以看出,replaySubject是可以對它send next(error,completed)的東西進行buffer的。
RACReplaySubject是繼承自RACSubject的,它的內部的實現例如subscribe:、sendNext:的實現也會呼叫super的實現

  • 從init中我們看出,RACReplaySubject物件持有capacity變數(用於決定valuesReceived快取多少個sendNext:出來的value,這在區分replay和replayLast的時候特別有用)以及valuesReceived陣列(用來儲存sendNext:出來的value),這二者接下來會重點涉及到

  • 從subscribe:可以看出,RACReplaySubject物件每次subscription,都會把之前valuesReceived中buffer的value重新sendNext一遍,然後呼叫super把當前的subscriber加入到subscribers陣列中

從sendNext:可以看出,RACReplaySubject物件會buffer每次sendNext的value,然後會呼叫super,對subscribers中的每個subscriber,呼叫sendNext。buffer的數量是根據self.capacity來決定的

———————惱人的分隔線 end——————

介紹完了RACReplaySubject之後,我們繼續進行multicast的part 2部分。
在上面的例子中,我們對connection.signal進行了兩次subscription,結合上面的RACReplaySubject的subscription的subscribe:,我們得到以下過程:

  1. [RACReplaySubject subject]會將這兩次subscription過程中的subscriber都儲存在subscribers陣列中
  2. 當網路請求success後,會[subscriber sendNext:response],前面已經講過這個subscriber就是[RACReplaySubject subject],這樣,就會把sendNext:的value儲存在valuesReceived陣列中,供後續subscription使用(不知道你是否注意到RACReplaySubject的subscribe:中有個for迴圈),然後對subscribers中儲存的每個subscriber執行sendNext。

後續思考

  1. 上面討論的是RACReplaySubject物件先進行subscription,再進行sendNext,如果是先sendNext,再subscription呢?其實魅力就在於RACReplaySubject的subscribe:中的for迴圈。具體過程留作思考
  2. 在RACSignal+Operation中關於multicast && replay的,一共有5個操作:publish、multicast、replay、replayLast、replayLazily,他們之間有什麼細微的差別呢?相信在我上面內容的基礎上,他們之間的細微差別不難理解,這裡推薦一篇幫助大家理解的blog

參考資料

ReactiveCocoa github主頁
ReactiveCocoa Documentation
ReactiveCocoa raywenderlich上的資料

相關文章