一直搞不明白replayLazily可以直接跳到最後看。
原文:http://spin.atomicobject.com/2014/06/29/replay-replaylast-replaylazily/
最近同事問我在ReactiveCoca中replay,replayLast和replayLazily有什麼區別,但我對此也是一知半解,也不能完整描述出來它們之間的不同,所以,我將對它們進行深入研究。
如果你沒有對RACReplaySubject和RACMulticastConnection有很好的理解的話,你會對它們在頭文件中的描述理解的很困難。現在不去了解底層原理,我會嘗試去描述解析這些方法。
對於一個“普通”的信號,每次訂閱都將會導致信號中的代碼再執行一遍,且該次訂閱者僅接收到該次訂閱發送出去的值。
第一個例子演示每次訂閱都會重新執行訂閱代碼。
__block int num = 0; RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) { num++; NSLog(@"Increment num to: %i", num); [subscriber sendNext:@(num)]; return nil; }]; NSLog(@"Start subscriptions"); // Subscriber 1 (S1) [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; // Subscriber 2 (S2) [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; // Subscriber 3 (S3) [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
運行結果如下:
Start subscriptions Increment num to: 1 S1: 1 Increment num to: 2 S2: 2 Increment num to: 3 S3: 3
可以看到,每次訂閱num都在遞增,如果不訂閱則不會遞增。通過這種方式,可以知道信號是懶惰的,如果沒有訂閱者的話,是不會執行的。
第二個例子演示信號被添加訂閱的時候,訂閱者是怎麼接收發送的值的。
RACSubject *letters = [RACSubject subject]; RACSignal *signal = letters; NSLog(@"Subscribe S1"); [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; NSLog(@"Send A"); [letters sendNext:@"A"]; NSLog(@"Send B"); [letters sendNext:@"B"]; NSLog(@"Subscribe S2"); [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; NSLog(@"Send C"); [letters sendNext:@"C"]; NSLog(@"Send D"); [letters sendNext:@"D"]; NSLog(@"Subscribe S3"); [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
運行結果
Subscribe S1 Send A S1: A Send B S1: B Subscribe S2 Send C S1: C S2: C Send D S1: D S2: D Subscribe S3
在很多情況下,這是我們想要的預期結果,不過在某些情況下,你不需要訂閱的代碼再次被執行。例如訂閱 一個向網絡服務器發送的請求,當服務器返回數據時,多個監聽者需要更新(無論有多少個監聽者,請求只發送一下(第一個例子就不滿足我們的需求)),或者我們想拿到訂閱前信號發送過的值(第二個例子,S2想拿A,B的值或者S3想拿A,B,C,D的值,就不滿足我們的需求了)。因此-replay
,-replayLast
, and-replayLazily應需而生。
這個replay方法將返回一個新的信號,當源信號被訂閱時,會立即發送給訂閱者全部歷史的值,不會重復執行源信號中的訂閱代碼,不僅如此,訂閱者還將收到所有未來發送過去的值。
第一個例子演示信號添加新的訂閱時,代碼是不會再次被執行的。
__block int num = 0; RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) { num++; NSLog(@"Increment num to: %i", num); [subscriber sendNext:@(num)]; return nil; }] replay]; NSLog(@"Start subscriptions"); // Subscriber 1 (S1) [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; // Subscriber 2 (S2) [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; // Subscriber 3 (S3) [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
Increment num to: 1 Start subscriptions S1: 1 S2: 1 S3: 1
信號首次被訂閱時,num立馬被遞增了,且僅僅遞增了一次。這說明了不管有你多個訂閱者,訂閱代碼我只執行了一次。
第二個例子演示每個新添加的訂閱者接收到信號中全部的值(不管是之前發出的值還是將來發出的值)。
RACSubject *letters = [RACSubject subject]; RACSignal *signal = [letters replay]; NSLog(@"Subscribe S1"); [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; NSLog(@"Send A"); [letters sendNext:@"A"]; NSLog(@"Send B"); [letters sendNext:@"B"]; NSLog(@"Subscribe S2"); [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; NSLog(@"Send C"); [letters sendNext:@"C"]; NSLog(@"Send D"); [letters sendNext:@"D"]; NSLog(@"Subscribe S3"); [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
Subscribe S1 Send A S1: A Send B S1: B Subscribe S2 S2: A S2: B Send C S1: C S2: C Send D S1: D S2: D Subscribe S3 S3: A S3: B S3: C S3: D
盡管訂閱者S3在所有的值發送之後再訂閱,然後還能接收到所有的值。
這個replayLast返回一個新的信號,當源信號被訂閱時,會立即發送給訂閱者最新的值,不會重復執行源信號中的訂閱代碼。訂閱者還會收到信號未來所有的值。
對於第一個例子,跟之前replay一樣,所以我就不再次演示了。
第二個例子演示如何將最新的值提供給新的訂閱者
RACSubject *letters = [RACSubject subject]; RACSignal *signal = [letters replayLast]; NSLog(@"Subscribe S1"); [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; NSLog(@"Send A"); [letters sendNext:@"A"]; NSLog(@"Send B"); [letters sendNext:@"B"]; NSLog(@"Subscribe S2"); [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; NSLog(@"Send C"); [letters sendNext:@"C"]; NSLog(@"Send D"); [letters sendNext:@"D"]; NSLog(@"Subscribe S3"); [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
Subscribe S1 Send A S1: A Send B S1: B Subscribe S2 S2: B Send C S1: C S2: C Send D S1: D S2: D Subscribe S3 S3: D
這replayLazily方法返回一個新的信號,當源信號被訂閱時,會立即發送給訂閱者全部歷史的值,不會重復執行源信號中的訂閱代碼。跟replay不同的是,replayLazily被訂閱生成新的信號之前是不會對源信號進行訂閱的(原文寫的有點繞,簡單來講 直到訂閱時候才真正創建一個信號,源信號的訂閱代碼才開始執行)。暫時不理解也沒事,看下面的代碼輸出,和注釋。
這第一個例子會說明跟replay差異。 注意字符串“Increment num to: 1”是被訂閱了之後才打印顯示的。而replay和replayLast沒被訂閱前就打印了“Increment num to: 1” 這個消息。
__block int num = 0; RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id subscriber) { num++; NSLog(@"Increment num to: %i", num); [subscriber sendNext:@(num)]; return nil; }] replayLazily]; //跟replay不同的就這麼一個地方 NSLog(@"Start subscriptions"); // Subscriber 1 (S1) [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; // Subscriber 2 (S2) [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; // Subscriber 3 (S3) [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
// 帖子滾動起來,跟replay比較一下 Increment num to: 1 的顯示順序。 Start subscriptions // 實際訂閱 Increment num to: 1 // 信號開始創建 S1: 1 S2: 1 S3: 1
第二個例子演示將全部歷史的值提供給任何新的訂閱者,就像replay一樣。
RACSubject *letters = [RACSubject subject]; RACSignal *signal = [letters replayLazily]; NSLog(@"Subscribe S1"); [signal subscribeNext:^(id x) { NSLog(@"S1: %@", x); }]; NSLog(@"Send A"); [letters sendNext:@"A"]; NSLog(@"Send B"); [letters sendNext:@"B"]; NSLog(@"Subscribe S2"); [signal subscribeNext:^(id x) { NSLog(@"S2: %@", x); }]; NSLog(@"Send C"); [letters sendNext:@"C"]; NSLog(@"Send D"); [letters sendNext:@"D"]; NSLog(@"Subscribe S3"); [signal subscribeNext:^(id x) { NSLog(@"S3: %@", x); }];
Subscribe S1 Send A S1: A Send B S1: B Subscribe S2 S2: A S2: B Send C S1: C S2: C Send D S1: D S2: D Subscribe S3 S3: A S3: B S3: C S3: D
總結一下:
ReactiveCocoa提供了這三個簡便的方法允許多個訂閱者訂閱一個信號,卻不會重復執行訂閱代碼,並且能給新加的訂閱者提供訂閱前的值。replay和replayLast使信號變成熱信號,且會提供所有值(-replay
) 或者最新的值(-replayLast
) 給訂閱者。replayLazily返回一個冷的信號,會提供所有的值給訂閱者。