RACSignal的Subscription深入分析
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]來獲得signalreturn [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {[self.signInServicesignInWithUsername:self.usernameTextField.textpassword:self.passwordTextField.textcomplete:^(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
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<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; }步驟三:進入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<RACSubscriber> subscriber) {AFHTTPRequestOperation *operation = [clientHTTPRequestOperationWithRequest:requestsuccess:^(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); }];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]
- 結合上面的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
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數組
- 從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的實現。
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),這二者接下來會重點涉及到。
- 從subscribe:可以看出,RACReplaySubject對象每次subscription,都會把之前valuesReceived中buffer的value重新sendNext一遍,然后調用super把當前的subscriber加入到subscribers數組中。
從sendNext:可以看出,RACReplaySubject對象會buffer每次sendNext的value,然后會調用super,對subscribers中的每個subscriber,調用sendNext。buffer的數量是根據self.capacity來決定的。
介紹完了RACReplaySubject之后,我們繼續進行multicast的part 2部分。
在上面的例子中,我們對connection.signal進行了兩次subscription,結合上面的RACReplaySubject的subscription的subscribe:,我們得到以下過程:
后續思考
參考資料
ReactiveCocoa github主頁 ReactiveCocoa Documentation ReactiveCocoa raywenderlich上的資料
總結
以上是生活随笔為你收集整理的RACSignal的Subscription深入分析的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Spring Boot整合MyBatis
- 下一篇: Spark性能优化指南——高级篇