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的常見用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
-(RACSignal *)signInSignal { // part 1:[RACSignal createSignal]來獲得signal return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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
1 2 3 4 5 6 7 8 9 10 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
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<RACSubscriber>)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
1 2 3 4 5 6 7 8 |
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。
為什麼要清楚這兩者的原理
1 2 3 4 5 6 7 8 9 10 |
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.”
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
// 引用ReactiveCocoa原始碼的Documentation目錄下的一個例子 // This signal starts a new request on each subscription. RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> 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:
1 2 3 4 5 6 7 8 9 10 11 12 |
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]
1 2 3 4 5 6 7 8 |
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的一個用法如下:
1 2 3 4 5 6 7 |
RACSubject *letters = [RACSubject subject]; // Outputs: A B [letters subscribeNext:^(id x) { NSLog(@"%@ ", x); }]; [letters sendNext:@"A"]; [letters sendNext:@"B"]; |
接下來分析RACSubject的原理
1 2 3 4 5 6 7 8 9 |
RACSubject.m中: - (id)init { self = [super init]; if (self == nil) return nil; _disposable = [RACCompoundDisposable compoundDisposable]; _subscribers = [[NSMutableArray alloc] initWithCapacity:1]; return self; } |
- RACSubject中有一個subscribers陣列
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
RACSubject.m中: - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { NSCParameterAssert(subscriber != nil); RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; subscriber = [[RACPassthroughSubscriber alloc] initWithSubscriber:subscriber signal:self disposable:disposable]; NSMutableArray *subscribers = self.subscribers; @synchronized (subscribers) { [subscribers addObject:subscriber]; } return [RACDisposable disposableWithBlock:^{ @synchronized (subscribers) { // Since newer subscribers are generally shorter-lived, search // starting from the end of the list. NSUInteger index = [subscribers indexOfObjectWithOptions:NSEnumerationReverse passingTest:^ BOOL (id<RACSubscriber> obj, NSUInteger index, BOOL *stop) { return obj == subscriber; }]; if (index != NSNotFound) [subscribers removeObjectAtIndex:index]; } }]; } |
- 從subscribe:的實現可以看出,對RACSubject物件的每次subscription,都是將這個subscriber加到subscribers陣列中而已
1 2 3 4 5 6 |
RACSubject.m中: - (void)sendNext:(id)value { [self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { [subscriber sendNext:value]; }]; } |
- 從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的實現
1 2 3 4 5 6 7 8 9 10 |
RACReplaySubject.m中: - (instancetype)initWithCapacity:(NSUInteger)capacity { self = [super init]; if (self == nil) return nil; _capacity = capacity; _valuesReceived = (capacity == RACReplaySubjectUnlimitedCapacity ? [NSMutableArray array] : [NSMutableArray arrayWithCapacity:capacity]); return self; } |
- 從init中我們看出,RACReplaySubject物件持有capacity變數(用於決定valuesReceived快取多少個sendNext:出來的value,這在區分replay和replayLast的時候特別有用)以及valuesReceived陣列(用來儲存sendNext:出來的value),這二者接下來會重點涉及到
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
RACReplaySubject.m中: - (RACDisposable *)subscribe:(id<RACSubscriber>)subscriber { RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable]; RACDisposable *schedulingDisposable = [RACScheduler.subscriptionScheduler schedule:^{ @synchronized (self) { for (id value in self.valuesReceived) { if (compoundDisposable.disposed) return; [subscriber sendNext:(value == RACTupleNil.tupleNil ? nil : value)]; } if (compoundDisposable.disposed) return; if (self.hasCompleted) { [subscriber sendCompleted]; } else if (self.hasError) { [subscriber sendError:self.error]; } else { RACDisposable *subscriptionDisposable = [super subscribe:subscriber]; [compoundDisposable addDisposable:subscriptionDisposable]; } } }]; [compoundDisposable addDisposable:schedulingDisposable]; return compoundDisposable; } |
- 從subscribe:可以看出,RACReplaySubject物件每次subscription,都會把之前valuesReceived中buffer的value重新sendNext一遍,然後呼叫super把當前的subscriber加入到subscribers陣列中
1 2 3 4 5 6 7 8 9 10 |
RACReplaySubject.m中: - (void)sendNext:(id)value { @synchronized (self) { [self.valuesReceived addObject:value ?: RACTupleNil.tupleNil]; [super sendNext:value]; if (self.capacity != RACReplaySubjectUnlimitedCapacity && self.valuesReceived.count > self.capacity) { [self.valuesReceived removeObjectsInRange:NSMakeRange(0, self.valuesReceived.count - self.capacity)]; } } } |
從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上的資料