日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

RACSignal的Subscription深入分析

發布時間:2024/7/5 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 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
  • [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<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

    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); }];
  • 在上面的例子中,如果我們不用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

    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數組
    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數組中而已
    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的實現。

    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),這二者接下來會重點涉及到。
    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數組中。
    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來決定的。

    介紹完了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上的資料

    總結

    以上是生活随笔為你收集整理的RACSignal的Subscription深入分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。