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的常見用法 -(RACSignal *)signInSignal { // part 1:[RACSignal createSignal]來獲得signal return [RACSignal createSignal:^RACDisposable *(id subscriber) { [self.signInService signInWithUsername:self.usernameTextField.text password:self.passwordTextField.text complete:^(BOOL success) { // part 3: 進入didSubscribe,通過[subscriber sendNext:]來執行next block [subscriber sendNext:@(success)]; [subscriber sendCompleted]; }]; return nil; }]; } // part 2 : [signal subscribeNext:]來獲得subscriber,然後進行subscription [[self signInSignal] subscribeNext:^(id x) { NSLog(@"Sign in result: %@", x); }]; Subscription過程概括 RACSignal的Subscription過程概括起來可以分為三個步驟: [RACSignal createSignal]來獲得signal [signal subscribeNext:]來獲得subscriber,然後進行subscription 進入didSubscribe,通過[subscriber sendNext:]來執行next block 步驟一:[RACSignal createSignal]來獲得signal RACSignal.m中: + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } [RACSignal createSignal]會調用子類RACDynamicSignal的createSignal來返回一個signal,並在signal中保存後面的 didSubscribe這個block 步驟二:[signal subscribeNext:]來獲得subscriber,然後進行subscription RACSignal.m中: - ( RACDisposable *)subscribeNext:( void (^)( id x))nextBlock { RACSubscriber *o = [ RACSubscriber subscriberWithNext :nextBlock error : NULL completed : NULL ]; return [ self subscribe :o]; } RACSubscriber.m中: + ( instancetype )subscriberWithNext:( void (^)( id x))next error:( void (^)( NSError *error))error completed:( void (^)( void ))completed { RACSubscriber *subscriber = [[ self alloc ] init ]; subscriber-> _next = [next copy ]; subscriber-> _error = [error copy ]; subscriber-> _completed = [completed copy ]; return subscriber; } RACDynamicSignal.m中: - (RACDisposable *)subscribe:(id)subscriber { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; if (self.didSubscribe != NULL) { RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ RACDisposable *innerDisposable = self.didSubscribe(subscriber); [disposable addDisposable:innerDisposable]; }]; [disposable addDisposable:schedulingDisposable]; } return disposable; } [signal subscribeNext]先會獲得一個subscriber,這個subscriber中保存了nextBlock、errorBlock、completedBlock 由於這個signal其實是RACDynamicSignal類型的,這個[self subscribe]方法會調用步驟一中保存的didSubscribe,參數就是1中的subscriber 步驟三:進入didSubscribe,通過[subscriber sendNext:]來執行next block RACSubscriber.m中: - (void)sendNext:(id)value { @synchronized (self) { void (^nextBlock)(id) = [self.next copy]; if (nextBlock == nil) return; nextBlock(value); } } 任何時候這個[subscriber sendNext:],就直接調用nextBlock signal的subscription過程回顧 從上面的三個步驟,我們看出: 先通過createSignal和subscribeNext這兩個調用,聲明了流中value到來時的處理方式 didSubscribe block塊中異步處理完畢之後,subscriber進行sendNext,自動處理 搞清楚了RAC的subscription過程,接著在此基礎上我們討論一個RACSignal中比較容易混淆的兩個操作:multicast和replay。 為什麼要清楚這兩者的原理 RACSignal+Operation.h中 - (RACMulticastConnection *)publish; - (RACMulticastConnection *)multicast:(RACSubject *)subject; - (RACSignal *)replay; - (RACSignal *)replayLast; - (RACSignal *)replayLazily; 在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." // 引用ReactiveCocoa源碼的Documentation目錄下的一個例子 // This signal starts a new request on each subscription. RACSignal *networkRequest = [RACSignal createSignal:^(id subscriber) { AFHTTPRequestOperation *operation = [client HTTPRequestOperationWithRequest:request success:^(AFHTTPRequestOperation *operation, id response) { [subscriber sendNext:response]; [subscriber sendCompleted]; } failure:^(AFHTTPRequestOperation *operation, NSError *error) { [subscriber sendError:error]; }]; [client enqueueHTTPRequestOperation:operation]; return [RACDisposable disposableWithBlock:^{ [operation cancel]; }]; }]; // Starts a single request, no matter how many subscriptions `connection.signal` // gets. This is equivalent to the -replay operator, or similar to // +startEagerlyWithScheduler:block:. RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]]; [connection connect]; [connection.signal subscribeNext:^(id response) { NSLog(@"subscriber one: %@", response); }]; [connection.signal subscribeNext:^(id response) { NSLog(@"subscriber two: %@", response); }]; 在上面的例子中,如果我們不用RACMulticastConnection的話,那就會因為執行了兩次subscription而導致發了兩次網絡請求。 從上面的例子中,我們可以看到對一個Signal進行multicast之後,我們是對connection.signal進行subscription而不是原來的networkRequest。這點是"side effects should only occur once"的關鍵,我們將在後面解釋 multicast原理分析 replay是multicast的一個特殊case而已,而multicast的整個過程可以拆分成兩個步驟,下面進行詳細討論 multicast的機制Part 1: RACMulticastConnection.m中: - (id)initWithSourceSignal:(RACSignal *)source subject:(RACSubject *)subject { NSCParameterAssert(source != nil); NSCParameterAssert(subject != nil); self = [super init]; if (self == nil) return nil; _sourceSignal = source; _serialDisposable = [[RACSerialDisposable alloc] init]; _signal = subject; return self; } 結合上面的例子來看,RACMulticastConnection的init是以networkRequest作為sourceSignal,而最終connnection.signal指的是[RACReplaySubject subject] RACMulticastConnection.m中: - (RACDisposable *)connect { BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected); if (shouldConnect) { self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal]; } return self.serialDisposable; } 結合上面的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 *letters = [RACSubject subject]; // Outputs: A B [letters subscribeNext:^(id x) { NSLog(@"%@ ", x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; 接下來分析RACSubject的原理 RACSubject.m中: - (id)init { self = [super init]; if (self == nil) return nil; _disposable = [RACCompoundDisposable compoundDisposable]; _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; return self; } RACSubject中有一個subscribers數組 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } 0 從subscribe:的實現可以看出,對RACSubject對象的每次subscription,都是將這個subscriber加到subscribers數組中而已 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } 1 從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的實現 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } 2 從init中我們看出,RACReplaySubject對象持有capacity變量(用於決定valuesReceived緩存多少個sendNext:出來的value,這在區分replay和replayLast的時候特別有用)以及valuesReceived數組(用來保存sendNext:出來的value),這二者接下來會重點涉及到 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } 3 從subscribe:可以看出,RACReplaySubject對象每次subscription,都會把之前valuesReceived中buffer的value重新sendNext一遍,然後調用super把當前的subscriber加入到subscribers數組中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { return [ RACDynamicSignal createSignal :didSubscribe]; } RACDynamicSignal.m中 + ( RACSignal *)createSignal:( RACDisposable * (^)( id < RACSubscriber > subscriber))didSubscribe { RACDynamicSignal *signal = [[ self alloc ] init ]; signal-> _didSubscribe = [didSubscribe copy ]; return [signal setNameWithFormat : @"+createSignal:" ]; } 4 從sendNext:可以看出,RACReplaySubject對象會buffer每次sendNext的value,然後會調用super,對subscribers中的每個subscriber,調用sendNext。buffer的數量是根據self.capacity來決定的 ---------------------惱人的分隔線 end------------------ 介紹完了RACReplaySubject之後,我們繼續進行multicast的part 2部分。 在上面的例子中,我們對connection.signal進行了兩次subscription,結合上面的RACReplaySubject的subscription的subscribe:,我們得到以下過程: [RACReplaySubject subject]會將這兩次subscription過程中的subscriber都保存在subscribers數組中 當網絡請求success後,會[subscriber sendNext:response],前面已經講過這個subscriber就是[RACReplaySubject subject],這樣,就會把sendNext:的value保存在valuesReceived數組中,供後續subscription使用(不知道你是否注意到RACReplaySubject的subscribe:中有個for循環),然後對subscribers中保存的每個subscriber執行sendNext。 後續思考 上面討論的是RACReplaySubject對象先進行subscription,再進行sendNext,如果是先sendNext,再subscription呢?其實魅力就在於RACReplaySubject的subscribe:中的for循環。具體過程留作思考 在RACSignal+Operation中關於multicast && replay的,一共有5個操作:publish、multicast、replay、replayLast、replayLazily,他們之間有什麼細微的差別呢?相信在我上面內容的基礎上,他們之間的細微差別不難理解,這裡推薦一篇幫助大家理解的blog
參考資料
ReactiveCocoa github主頁
ReactiveCocoa Documentation
ReactiveCocoa raywenderlich上的資料