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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号

發布時間:2024/7/5 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

第一篇文章中我們介紹了冷信號與熱信號的概念,前一篇文章我們也討論了為什么要區分冷信號與熱信號,下面我會先為大家揭曉熱信號的本質,再給出冷信號轉換成熱信號的方法。

揭示熱信號的本質

在ReactiveCocoa中,究竟什么才是熱信號呢?冷信號是比較常見的,map一下就會得到一個冷信號。但在RAC中,好像并沒有“hot signal”這個單獨的說法。原來在RAC的世界中,所有的熱信號都屬于一個類——RACSubject。接下來我們來看看究竟它為什么這么“神奇”。

在RAC2.5文檔的框架概述中,有著這樣一段描述:

A subject, represented by the RACSubject class, is a signal that can be manually controlled.

Subjects can be thought of as the “mutable” variant of a signal, much like NSMutableArray is for NSArray. They are extremely useful for bridging non-RAC code into the world of signals.

For example, instead of handling application logic in block callbacks, the blocks can simply send events to a shared subject instead. The subject can then be returned as a RACSignal, hiding the implementation detail of the callbacks.

Some subjects offer additional behaviors as well. In particular, RACReplaySubject can be used to buffer events for future subscribers, like when a network request finishes before anything is ready to handle the result.

從這段描述中,我們可以發現Subject具備如下三個特點:

  • Subject是“可變”的。
  • Subject是非RAC到RAC的一個橋梁。
  • Subject可以附加行為,例如RACReplaySubject具備為未來訂閱者緩沖事件的能力。
  • 從第三個特點來看,Subject具備為未來訂閱者緩沖事件的能力,那也就說明它是自身是有狀態的。根據上文的介紹,Subject是符合熱信號的特點的。為了驗證它,我們再來做個簡單實驗:

    RACSubject *subject = [RACSubject subject];RACSubject *replaySubject = [RACReplaySubject subject];[[RACScheduler mainThreadScheduler] afterDelay:0.1 schedule:^{// Subscriber 1[subject subscribeNext:^(id x) {NSLog(@"Subscriber 1 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 1 get a next value: %@ from replay subject", x);}];// Subscriber 2[subject subscribeNext:^(id x) {NSLog(@"Subscriber 2 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 2 get a next value: %@ from replay subject", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:1 schedule:^{[subject sendNext:@"send package 1"];[replaySubject sendNext:@"send package 1"];}];[[RACScheduler mainThreadScheduler] afterDelay:1.1 schedule:^{// Subscriber 3[subject subscribeNext:^(id x) {NSLog(@"Subscriber 3 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 3 get a next value: %@ from replay subject", x);}];// Subscriber 4[subject subscribeNext:^(id x) {NSLog(@"Subscriber 4 get a next value: %@ from subject", x);}];[replaySubject subscribeNext:^(id x) {NSLog(@"Subscriber 4 get a next value: %@ from replay subject", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[subject sendNext:@"send package 2"];[replaySubject sendNext:@"send package 2"];}];

    按照時間線來解讀一下上述代碼:

  • 0s時創建subject與replaySubject這兩個subject。
  • 0.1s時Subscriber 1分別訂閱了subject與replaySubject。
  • 0.1s時Subscriber 2也分別訂閱了subject與replaySubject。
  • 1s時分別向subject與replaySubject發送了"send package 1"這個字符串作為
  • 1.1s時Subscriber 3分別訂閱了subject與replaySubject。
  • 1.1s時Subscriber 4也分別訂閱了subject與replaySubject。
  • 2s時再分別向subject與replaySubject發送了"send package 2"這個字符串作為
  • 接下來看一下輸出的結果:

    2015-09-28 13:35:22.855 RACDemos[13646:1269269] Start 2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from subject 2015-09-28 13:35:23.856 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from subject 2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 1 from replay subject 2015-09-28 13:35:23.857 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 1 from replay subject 2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 1 from replay subject 2015-09-28 13:35:24.059 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 1 from replay subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from subject 2015-09-28 13:35:25.039 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 1 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 2 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 3 get a next value: send package 2 from replay subject 2015-09-28 13:35:25.040 RACDemos[13646:1269269] Subscriber 4 get a next value: send package 2 from replay subject

    結合結果可以分析出如下內容:

  • 22.855s時,測試啟動,subject與replaySubject創建完畢。
  • 23.856s時,距離啟動大約1s后,Subscriber 1和Subscriber 2同時從subject接收到了"send package 1"這個值。
  • 23.857s時,也是距離啟動大約1s后,Subscriber 1和Subscriber 2同時從replaySubject接收到了"send package 1"這個值。
  • 24.059s時,距離啟動大約1.2s后,Subscriber 3和Subscriber 4同時從replaySubject接收到了"send package 1"這個值。注意Subscriber 3和Subscriber 4并沒有從subject接收"send package 1"這個值。
  • 25.039s時,距離啟動大約2.1s后,Subscriber 1、Subscriber 2、Subscriber 3、Subscriber 4同時從subject接收到了"send package 2"這個值。
  • 25.040s時,距離啟動大約2.1s后,Subscriber 1、Subscriber 2、Subscriber 3、Subscriber 4同時從replaySubject接收到了"send package 2"這個值。
  • 只關注subject,根據時間線,我們可以得到下圖:

    經過觀察不難發現,4個訂閱者實際上是共享subject的,一旦這個subject發送了值,當前的訂閱者就會同時接收到。由于Subscriber 3與Subscriber 4的訂閱時間稍晚,所以錯過了第一次值的發送。這與冷信號是截然不同的反應。冷信號的圖類似下圖:

    對比上面兩張圖,是不是可以發現,subject類似“直播”,錯過了就不再處理。而signal類似“點播”,每次訂閱都會從頭開始。所以我們有理由認定subject天然就是熱信號。

    下面再來看看replaySubject,根據時間線,我們能得到另一張圖:

    將圖3與圖1對比會發現,Subscriber 3與Subscriber 4在訂閱后馬上接收到了“歷史值”。對于Subscriber 3和Subscriber 4來說,它們只關心“歷史的值”而不關心“歷史的時間線”,因為實際上1與2是間隔1s發送的,但是它們接收到的顯然不是。舉個生動的例子,就好像科幻電影里面主人公穿越時間線后會先把所有的回憶快速閃過再來到現實一樣。(見《X戰警:逆轉未來》、《蝴蝶效應》)所以我們也有理由認定replaySubject天然也是熱信號。

    看到這里,我們終于揭開了熱信號的面紗,結論就是:

  • RACSubject及其子類是熱信號
  • RACSignal排除RACSubject類以外的是冷信號
  • 如何將一個冷信號轉化成熱信號——廣播

    冷信號與熱信號的本質區別在于是否保持狀態,冷信號的多次訂閱是不保持狀態的,而熱信號的多次訂閱可以保持狀態。所以一種將冷信號轉換為熱信號的方法就是,將冷信號訂閱,訂閱到的每一個時間通過RACSbuject發送出去,其他訂閱者只訂閱這個RACSubject。

    觀察下面的代碼:

    RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[coldSignal subscribe:subject];}];[subject subscribeNext:^(id x) {NSLog(@"Subscriber 1 recieve value:%@.", x);}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[subject subscribeNext:^(id x) {NSLog(@"Subscriber 2 recieve value:%@.", x);}];

    執行順序是這樣的:

  • 創建一個冷信號:coldSignal。該信號聲明了“訂閱后1.5秒發送‘A’,3秒發送’B’,5秒發送完成事件”。
  • 創建一個RACSubject:subject。
  • 在2秒后使用這個subject訂閱coldSignal。
  • 立即訂閱這個subject。
  • 4秒后訂閱這個subject。
  • 如果所料不錯的話,通過訂閱這個subject并不會引起coldSignal重復執行block的內容。我們來看下結果:

    2015-09-28 19:36:45.703 RACDemos[14110:1556061] Subject created. 2015-09-28 19:36:47.705 RACDemos[14110:1556061] Cold signal be subscribed. 2015-09-28 19:36:49.331 RACDemos[14110:1556061] Subscriber 1 recieve value:A. 2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 1 recieve value:B. 2015-09-28 19:36:50.999 RACDemos[14110:1556061] Subscriber 2 recieve value:B.

    參考時間線,會得到下圖:

    不難發現其中的幾個重點: 1. subject是從一開始就創建好的,等到2s后便開始訂閱coldSignal。 2. Subscriber 1是subject創建后就開始訂閱的,但是第一個接收時間與subject接收coldSignal第一個值的時間是一樣的。 3. Subscriber 2是subject創建4s后開始訂閱的,所以只能接收到第二個值。

    通過觀察可以確定,subject就是coldSignal轉化的熱信號。所以使用RACSubject來將冷信號轉化為熱信號是可行的。

    當然,使用這種RACSubject來訂閱冷信號得到熱信號的方式仍有一些小的瑕疵。例如subject的訂閱者提前終止了訂閱,而subject并不能終止對coldSignal的訂閱。(RACDisposable是一個比較大的話題,我計劃在其他的文章中詳細闡述它,也希望感興趣的同學自己來理解。)所以在RAC庫中對于冷信號轉化成熱信號有如下標準的封裝:

    - (RACMulticastConnection *)publish; - (RACMulticastConnection *)multicast:(RACSubject *)subject; - (RACSignal *)replay; - (RACSignal *)replayLast; - (RACSignal *)replayLazily;

    這5個方法中,最為重要的就是- (RACMulticastConnection *)multicast:(RACSubject *)subject;這個方法了,其他幾個方法也是間接調用它的。我們來看看它的實現:

    /// implementation RACSignal (Operations) - (RACMulticastConnection *)multicast:(RACSubject *)subject {[subject setNameWithFormat:@"[%@] -multicast: %@", self.name, subject.name];RACMulticastConnection *connection = [[RACMulticastConnection alloc] initWithSourceSignal:self subject:subject];return connection; }/// implementation RACMulticastConnection- (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; }#pragma mark Connecting- (RACDisposable *)connect {BOOL shouldConnect = OSAtomicCompareAndSwap32Barrier(0, 1, &_hasConnected);if (shouldConnect) {self.serialDisposable.disposable = [self.sourceSignal subscribe:_signal];}return self.serialDisposable; }- (RACSignal *)autoconnect {__block volatile int32_t subscriberCount = 0;return [[RACSignalcreateSignal:^(id<RACSubscriber> subscriber) {OSAtomicIncrement32Barrier(&subscriberCount);RACDisposable *subscriptionDisposable = [self.signal subscribe:subscriber];RACDisposable *connectionDisposable = [self connect];return [RACDisposable disposableWithBlock:^{[subscriptionDisposable dispose];if (OSAtomicDecrement32Barrier(&subscriberCount) == 0) {[connectionDisposable dispose];}}];}]setNameWithFormat:@"[%@] -autoconnect", self.signal.name]; }

    雖然代碼比較短但不是很好懂,大概來說明一下:

  • 當RACSignal類的實例調用- (RACMulticastConnection *)multicast:(RACSubject *)subject時,以self和subject作為構造參數創建一個RACMulticastConnection實例。
  • RACMulticastConnection構造的時候,保存source和subject作為成員變量,創建一個RACSerialDisposable對象,用于取消訂閱。
  • 當RACMulticastConnection類的實例調用- (RACDisposable *)connect這個方法的時候,判斷是否是第一次。如果是的話用_signal這個成員變量來訂閱sourceSignal之后返回self.serialDisposable;否則直接返回self.serialDisposable。這里面訂閱sourceSignal是重點。
  • RACMulticastConnection的signal只讀屬性,就是一個熱信號,訂閱這個熱信號就避免了各種副作用的問題。它會在- (RACDisposable *)connect第一次調用后,根據sourceSignal的訂閱結果來傳遞事件。
  • 想要確保第一次訂閱就能成功訂閱sourceSignal,可以使用- (RACSignal *)autoconnect這個方法,它保證了第一個訂閱者觸發sourceSignal的訂閱,也保證了當返回的信號所有訂閱者都關閉連接后sourceSignal被正確關閉連接。
  • 由于RAC是一個線程安全的框架,所以好奇的同學可以了解下“OSAtomic*”這一系列的原子操作。拋開這些應該不難理解上述代碼。

    了解源碼之后,這個方法的正確使用就清楚了,應該像這樣:

    RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");RACMulticastConnection *multicastConnection = [coldSignal multicast:subject];RACSignal *hotSignal = multicastConnection.signal;[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[multicastConnection connect];}];[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 1 recieve value:%@.", x);}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 2 recieve value:%@.", x);}];}];

    或者這樣:

    RACSignal *coldSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {NSLog(@"Cold signal be subscribed.");[[RACScheduler mainThreadScheduler] afterDelay:1.5 schedule:^{[subscriber sendNext:@"A"];}];[[RACScheduler mainThreadScheduler] afterDelay:3 schedule:^{[subscriber sendNext:@"B"];}];[[RACScheduler mainThreadScheduler] afterDelay:5 schedule:^{[subscriber sendCompleted];}];return nil;}];RACSubject *subject = [RACSubject subject];NSLog(@"Subject created.");RACMulticastConnection *multicastConnection = [coldSignal multicast:subject];RACSignal *hotSignal = multicastConnection.autoconnect;[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 1 recieve value:%@.", x);}];}];[[RACScheduler mainThreadScheduler] afterDelay:4 schedule:^{[hotSignal subscribeNext:^(id x) {NSLog(@"Subscribe 2 recieve value:%@.", x);}];}];

    以上的兩種寫法和之前用Subject來傳遞的例子都可以得到相同的結果。

    下面再來看看其他幾個方法的實現:

    /// implementation RACSignal (Operations) - (RACMulticastConnection *)publish {RACSubject *subject = [[RACSubject subject] setNameWithFormat:@"[%@] -publish", self.name];RACMulticastConnection *connection = [self multicast:subject];return connection; }- (RACSignal *)replay {RACReplaySubject *subject = [[RACReplaySubject subject] setNameWithFormat:@"[%@] -replay", self.name];RACMulticastConnection *connection = [self multicast:subject];[connection connect];return connection.signal; }- (RACSignal *)replayLast {RACReplaySubject *subject = [[RACReplaySubject replaySubjectWithCapacity:1] setNameWithFormat:@"[%@] -replayLast", self.name];RACMulticastConnection *connection = [self multicast:subject];[connection connect];return connection.signal; }- (RACSignal *)replayLazily {RACMulticastConnection *connection = [self multicast:[RACReplaySubject subject]];return [[RACSignaldefer:^{[connection connect];return connection.signal;}]setNameWithFormat:@"[%@] -replayLazily", self.name]; }

    這幾個方法的實現都相當簡單,只是為了簡化而封裝,具體說明一下:

  • - (RACMulticastConnection *)publish就是幫忙創建了RACSubject。
  • - (RACSignal *)replay就是用RACReplaySubject來作為subject,并立即執行connect操作,返回connection.signal。其作用是上面提到的replay功能,即后來的訂閱者可以收到歷史值。
  • - (RACSignal *)replayLast就是用Capacity為1的RACReplaySubject來替換- (RACSignal *)replay的`subject。其作用是使后來訂閱者只收到最后的歷史值。
  • - (RACSignal *)replayLazily和- (RACSignal *)replay的區別就是replayLazily會在第一次訂閱的時候才訂閱sourceSignal。
  • 所以,其實本質仍然是

    使用一個Subject來訂閱原始信號,并讓其他訂閱者訂閱這個Subject,這個Subject就是熱信號。

    現在再回過來看下之前系列文章第二篇中那個業務場景的例子,其實修改的方法很簡單,就是在網絡獲取的fetchData這個信號后面,增加一個replayLazily變換,就不會出現網絡請求重發6次的問題了。

    修改后的代碼如下,大家可以試試:

    self.sessionManager = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:@"http://api.xxxx.com"]];self.sessionManager.requestSerializer = [AFJSONRequestSerializer serializer];self.sessionManager.responseSerializer = [AFJSONResponseSerializer serializer];@weakify(self)RACSignal *fetchData = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {@strongify(self)NSURLSessionDataTask *task = [self.sessionManager GET:@"fetchData" parameters:@{@"someParameter": @"someValue"} success:^(NSURLSessionDataTask *task, id responseObject) {[subscriber sendNext:responseObject];[subscriber sendCompleted];} failure:^(NSURLSessionDataTask *task, NSError *error) {[subscriber sendError:error];}];return [RACDisposable disposableWithBlock:^{if (task.state != NSURLSessionTaskStateCompleted) {[task cancel];}}];}] replayLazily]; // modify here!!RACSignal *title = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {if ([value[@"title"] isKindOfClass:[NSString class]]) {return [RACSignal return:value[@"title"]];} else {return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];}}];RACSignal *desc = [fetchData flattenMap:^RACSignal *(NSDictionary *value) {if ([value[@"desc"] isKindOfClass:[NSString class]]) {return [RACSignal return:value[@"desc"]];} else {return [RACSignal error:[NSError errorWithDomain:@"some error" code:400 userInfo:@{@"originData": value}]];}}];RACSignal *renderedDesc = [desc flattenMap:^RACStream *(NSString *value) {NSError *error = nil;RenderManager *renderManager = [[RenderManager alloc] init];NSAttributedString *rendered = [renderManager renderText:value error:&error];if (error) {return [RACSignal error:error];} else {return [RACSignal return:rendered];}}];RAC(self.someLablel, text) = [[title catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];RAC(self.originTextView, text) = [[desc catchTo:[RACSignal return:@"Error"]] startWith:@"Loading..."];RAC(self.renderedTextView, attributedText) = [[renderedDesc catchTo:[RACSignal return:[[NSAttributedString alloc] initWithString:@"Error"]]] startWith:[[NSAttributedString alloc] initWithString:@"Loading..."]];[[RACSignal merge:@[title, desc, renderedDesc]] subscribeError:^(NSError *error) {UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"Error" message:error.domain delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];[alertView show];}];

    當然,細心的同學會發現這樣修改,仍然有許多計算上的浪費,例如將fetchData轉換為title的block會執行多次,將fetchData轉換為desc的block也會執行多次。但是由于這些block都是無副作用的,計算量并不大,可以忽略不計。如果計算量大的,也需要對中間的信號進行熱信號的轉換。不過請不要忽略冷熱信號的轉換本身也是有計算代價的。

    好的,寫到這里,我們終于揭開RAC中冷信號與熱信號的全部面紗,也知道如何使用了。希望這個系列文章可以讓大家更好地了解RAC,避免使用RAC遇到的誤區。謝謝大家。

    美團iOS組有很多志同道合的小伙伴,對于各種技術都有著深入的了解,我們熱忱地歡迎一切牛掰的小伙伴加入,共同學習,共同進步。(簡歷請發送到郵箱 liangsi02@meituan.com)

    總結

    以上是生活随笔為你收集整理的细说ReactiveCocoa的冷信号与热信号(三):怎么处理冷信号与热信号的全部內容,希望文章能夠幫你解決所遇到的問題。

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