前言
緊接著上篇的原始碼實現分析,繼續分析RACSignal的變換操作的底層實現。
目錄
- 1.過濾操作
- 2.組合操作
一. 過濾操作
過濾操作也屬於一種變換,根據過濾條件,過濾出符合條件的值。變換出來的新的訊號是原訊號的一個子集。
1. filter: (在父類RACStream中定義的)
這個filter:操作在any:的實現中用到過了。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (instancetype)filter:(BOOL (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^ id (id value) { if (block(value)) { return [class return:value]; } else { return class.empty; } }] setNameWithFormat:@"[%@] -filter:", self.name]; } |
filter:中傳入一個閉包,是用篩選的條件。如果滿足篩選條件的即返回原訊號的值,否則原訊號的值被“吞”掉,返回空的訊號。這個變換主要是用flattenMap的。
2. ignoreValues
1 2 3 4 5 |
- (RACSignal *)ignoreValues { return [[self filter:^(id _) { return NO; }] setNameWithFormat:@"[%@] -ignoreValues", self.name]; } |
由上面filter的實現,這裡把篩選判斷條件永遠的傳入NO,那麼原訊號的值都會被變換成empty訊號,故變換之後的訊號為空訊號。
3. ignore: (在父類RACStream中定義的)
1 2 3 4 5 |
- (instancetype)ignore:(id)value { return [[self filter:^ BOOL (id innerValue) { return innerValue != value && ![innerValue isEqual:value]; }] setNameWithFormat:@"[%@] -ignore: %@", self.name, [value rac_description]]; } |
ignore:的實現還是由filter:實現的。傳入的篩選判斷條件是一個值,當原訊號傳送的值中是這個值的時候,就替換成空訊號。
4. distinctUntilChanged (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
- (instancetype)distinctUntilChanged { Class class = self.class; return [[self bind:^{ __block id lastValue = nil; __block BOOL initial = YES; return ^(id x, BOOL *stop) { if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; initial = NO; lastValue = x; return [class return:x]; }; }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; } |
distinctUntilChanged的實現是用bind來完成的。每次變換中都記錄一下原訊號上一次傳送過來的值,並與這一次進行比較,如果是相同的值,就“吞”掉,返回empty訊號。只有和原訊號上一次傳送的值不同,變換後的新訊號才把這個值傳送出來。
關於distinctUntilChanged,這裡關注的是兩兩訊號之間的值是否不同,有時候我們可能需要一個類似於NSSet的訊號集,distinctUntilChanged就無法滿足了。在ReactiveCocoa 2.5的這個版本也並沒有向我們提供distinct的變換函式。
我們可以自己實現類似的變換。實現思路也不難,可以把之前每次傳送過來的訊號都用陣列存起來,新來的訊號都去陣列裡面查詢一遍,如果找不到,就把這個值傳送出去,如果找到了,就返回empty訊號。效果如上圖。
5. take: (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- (instancetype)take:(NSUInteger)count { Class class = self.class; if (count == 0) return class.empty; return [[self bind:^{ __block NSUInteger taken = 0; return ^ id (id value, BOOL *stop) { if (taken < count) { ++taken; if (taken == count) *stop = YES; return [class return:value]; } else { return nil; } }; }] setNameWithFormat:@"[%@] -take: %lu", self.name, (unsigned long)count]; } |
take:實現也非常簡單,藉助bind函式來實現的。入參的count是原訊號取值的個數。在bind的閉包中,taken計數從0開始取原訊號的值,當taken取到count個數的時候,就停止取值。
在take:的基礎上我們還可以繼續改造出新的變換方式。比如說,想取原訊號中執行的第幾個值。類似於elementAt的操作。這個操作在ReactiveCocoa 2.5的這個版本也並沒有直接向我們提供出來。
其實實現很簡單,只需要判斷taken是否等於我們要取的那個位置就可以了,等於的時候把原訊號的值傳送出來,並*stop = YES。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
// 我自己增加實現的方法 - (instancetype)elementAt:(NSUInteger)index { Class class = self.class; return [[self bind:^{ __block NSUInteger taken = 0; return ^ id (id value, BOOL *stop) { if (index == 0) { *stop = YES; return [class return:value]; } if (taken == index) { *stop = YES; return [class return:value]; } else if (taken < index){ taken ++; return [class empty]; }else { return nil; } }; }] setNameWithFormat:@"[%@] -elementAt: %lu", self.name, (unsigned long)index]; } |
6. takeLast:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
- (RACSignal *)takeLast:(NSUInteger)count { return [[RACSignal createSignal:^(id subscriber) { NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count]; return [self subscribeNext:^(id x) { [valuesTaken addObject:x ? : RACTupleNil.tupleNil]; while (valuesTaken.count > count) { [valuesTaken removeObjectAtIndex:0]; } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ for (id value in valuesTaken) { [subscriber sendNext:value == RACTupleNil.tupleNil ? nil : value]; } [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -takeLast: %lu", self.name, (unsigned long)count]; } |
takeLast:的實現也是按照套路來。先建立一個新訊號,return的時候訂閱原訊號。在函式內部用一個valuesTaken來儲存原訊號傳送過來的值,原訊號發多少,就存多少,直到個數溢位入參給定的count,就溢位陣列第0位。這樣能保證陣列裡面始終都裝著最後count個原訊號的值。
當原訊號傳送completed訊號的時候,把陣列裡面存的值都sendNext出去。這裡要注意的也是該變換髮送訊號的時機。如果原訊號一直沒有completed,那麼takeLast:就一直沒法發出任何訊號來。
7. takeUntilBlock: (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (instancetype)takeUntilBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); Class class = self.class; return [[self bind:^{ return ^ id (id value, BOOL *stop) { if (predicate(value)) return nil; return [class return:value]; }; }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; } |
takeUntilBlock:是根據傳入的predicate閉包作為篩選條件的。一旦predicate( )閉包滿足條件,那麼新訊號停止傳送新訊號,因為它被置為nil了。和函式名的意思是一樣的,take原訊號的值,Until直到閉包滿足條件。
8. takeWhileBlock: (在父類RACStream中定義的)
1 2 3 4 5 6 7 |
- (instancetype)takeWhileBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); return [[self takeUntilBlock:^ BOOL (id x) { return !predicate(x); }] setNameWithFormat:@"[%@] -takeWhileBlock:", self.name]; } |
takeWhileBlock:的訊號集是takeUntilBlock:的訊號集的補集。全集是原訊號。takeWhileBlock:底層還是呼叫takeUntilBlock:,只不過判斷條件的是不滿足predicate( )閉包的集合。
9. takeUntil:
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 |
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger { return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; void (^triggerCompletion)(void) = ^{ [disposable dispose]; [subscriber sendCompleted]; }; RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) { triggerCompletion(); } completed:^{ triggerCompletion(); }]; [disposable addDisposable:triggerDisposable]; if (!disposable.disposed) { RACDisposable *selfDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [disposable dispose]; [subscriber sendCompleted]; }]; [disposable addDisposable:selfDisposable]; } return disposable; }] setNameWithFormat:@"[%@] -takeUntil: %@", self.name, signalTrigger]; } |
takeUntil:的實現也是“經典套路”——return一個新訊號,在新訊號中訂閱原訊號。入參是一個訊號signalTrigger,這個訊號是一個Trigger。一旦signalTrigger發出第一個訊號,就會觸發triggerCompletion( )閉包,在這個閉包中,會呼叫triggerCompletion( )閉包。
1 2 3 4 |
void (^triggerCompletion)(void) = ^{ [disposable dispose]; [subscriber sendCompleted]; }; |
一旦呼叫了triggerCompletion( )閉包,就會把原訊號取消訂閱,並給變換的新的訊號訂閱者sendCompleted。
如果入參signalTrigger一直沒有sendNext,那麼原訊號就會一直sendNext:。
10. takeUntilReplacement:
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 *)takeUntilReplacement:(RACSignal *)replacement { return [RACSignal createSignal:^(id subscriber) { RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *replacementDisposable = [replacement subscribeNext:^(id x) { [selfDisposable dispose]; [subscriber sendNext:x]; } error:^(NSError *error) { [selfDisposable dispose]; [subscriber sendError:error]; } completed:^{ [selfDisposable dispose]; [subscriber sendCompleted]; }]; if (!selfDisposable.disposed) { selfDisposable.disposable = [[self concat:[RACSignal never]] subscribe:subscriber]; } return [RACDisposable disposableWithBlock:^{ [selfDisposable dispose]; [replacementDisposable dispose]; }]; }]; } |
- 原訊號concat:了一個[RACSignal never]訊號,這樣原訊號就一直不會disposed,會一直等待replacement訊號的到來。
- 控制selfDisposable是否被dispose,控制權來自於入參的replacement訊號,一旦replacement訊號sendNext,那麼原訊號就會取消訂閱,接下來的事情就會交給replacement訊號了。
- 變換後的新訊號sendNext,sendError,sendCompleted全部都由replacement訊號來傳送,最終新訊號完成的時刻也是replacement訊號完成的時刻。
11. skip: (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (instancetype)skip:(NSUInteger)skipCount { Class class = self.class; return [[self bind:^{ __block NSUInteger skipped = 0; return ^(id value, BOOL *stop) { if (skipped >= skipCount) return [class return:value]; skipped++; return class.empty; }; }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; } |
skip:訊號集和take:訊號集是補集關係,全集是原訊號。take:是取原訊號的前count個訊號,而skip:是從原訊號第count + 1位開始取訊號。
skipped是一個遊標,每次原訊號傳送一個值,就比較它和入參skipCount的大小。如果不比skipCount大,說明還需要跳過,所以就返回empty訊號,否則就把原訊號的值傳送出來。
通過類比take系列方法,可以發現在ReactiveCocoa 2.5的這個版本也並沒有向我們提供skipLast:的變換函式。這個變換函式的實現過程也不難,我們可以類比takeLast:來實現。
實現的思路也不難,原訊號每次傳送過來的值,都用一個陣列儲存起來。skipLast:是想去掉原訊號最末尾的count個訊號。
我們先來分析一下:假設原訊號有n個訊號,從0 – (n-1),去掉最後的count個,前面還剩n – count個訊號。那麼從 原訊號的第 count + 1位的訊號開始傳送,到原訊號結束,這樣中間就正好是傳送了 n – count 個訊號。
分析清楚後,程式碼就很容易了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 我自己增加實現的方法 - (RACSignal *)skipLast:(NSUInteger)count { return [[RACSignal createSignal:^(id subscriber) { NSMutableArray *valuesTaken = [NSMutableArray arrayWithCapacity:count]; return [self subscribeNext:^(id x) { [valuesTaken addObject:x ? : RACTupleNil.tupleNil]; while (valuesTaken.count > count) { [subscriber sendNext:valuesTaken[0] == RACTupleNil.tupleNil ? nil : valuesTaken[0]]; [valuesTaken removeObjectAtIndex:0]; } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -skipLast: %lu", self.name, (unsigned long)count]; } |
原訊號每傳送過來一個訊號就存入陣列,當陣列裡面的個數大於count的時候,就是需要我們傳送訊號的時候,這個時候每次都把陣列裡面第0位傳送出去即可,陣列維護了一個FIFO的佇列。這樣就實現了skipLast:的效果了。
12. skipUntilBlock: (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (instancetype)skipUntilBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); Class class = self.class; return [[self bind:^{ __block BOOL skipping = YES; return ^ id (id value, BOOL *stop) { if (skipping) { if (predicate(value)) { skipping = NO; } else { return class.empty; } } return [class return:value]; }; }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; } |
skipUntilBlock: 的實現可以類比takeUntilBlock: 的實現。
skipUntilBlock: 是根據傳入的predicate閉包作為篩選條件的。一旦predicate( )閉包滿足條件,那麼skipping = NO。skipping為NO,以後原訊號傳送的每個值都原封不動的傳送出去。predicate( )閉包不滿足條件的時候,即會一直skip原訊號的值。和函式名的意思是一樣的,skip原訊號的值,Until直到閉包滿足條件,就不再skip了。
13. skipWhileBlock: (在父類RACStream中定義的)
1 2 3 4 5 6 7 |
- (instancetype)skipWhileBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); return [[self skipUntilBlock:^ BOOL (id x) { return !predicate(x); }] setNameWithFormat:@"[%@] -skipWhileBlock:", self.name]; } |
skipWhileBlock:的訊號集是skipUntilBlock:的訊號集的補集。全集是原訊號。skipWhileBlock:底層還是呼叫skipUntilBlock:,只不過判斷條件的是不滿足predicate( )閉包的集合。
到這裡skip系列方法就結束了,對比take系列的方法,少了2個方法,在ReactiveCocoa 2.5的這個版本中 takeUntil: 和 takeUntilReplacement:這兩個方法沒有與之對應的skip方法。
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 33 34 35 36 37 38 39 |
// 我自己增加實現的方法 - (RACSignal *)skipUntil:(RACSignal *)signalTrigger { return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; __block BOOL sendTrigger = NO; void (^triggerCompletion)(void) = ^{ sendTrigger = YES; }; RACDisposable *triggerDisposable = [signalTrigger subscribeNext:^(id _) { triggerCompletion(); } completed:^{ triggerCompletion(); }]; [disposable addDisposable:triggerDisposable]; if (!disposable.disposed) { RACDisposable *selfDisposable = [self subscribeNext:^(id x) { if (sendTrigger) { [subscriber sendNext:x]; } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [disposable dispose]; [subscriber sendCompleted]; }]; [disposable addDisposable:selfDisposable]; } return disposable; }] setNameWithFormat:@"[%@] -skipUntil: %@", self.name, signalTrigger]; } |
skipUntil實現方法也很簡單,當入參的signalTrigger開發傳送訊號的時候,就讓原訊號sendNext把值傳送出來,否則就把原訊號的值“吞”掉。
skipUntilReplacement:就沒什麼意義了,把原訊號經過skipUntilReplacement:變換之後得到的新的訊號就是Replacement訊號。所以說這個操作也就沒意義了。
14. groupBy:transform:
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 |
- (RACSignal *)groupBy:(id (^)(id object))keyBlock transform:(id (^)(id object))transformBlock { NSCParameterAssert(keyBlock != NULL); return [[RACSignal createSignal:^(id subscriber) { NSMutableDictionary *groups = [NSMutableDictionary dictionary]; NSMutableArray *orderedGroups = [NSMutableArray array]; return [self subscribeNext:^(id x) { id key = keyBlock(x); RACGroupedSignal *groupSubject = nil; @synchronized(groups) { groupSubject = groups[key]; if (groupSubject == nil) { groupSubject = [RACGroupedSignal signalWithKey:key]; groups[key] = groupSubject; [orderedGroups addObject:groupSubject]; [subscriber sendNext:groupSubject]; } } [groupSubject sendNext:transformBlock != NULL ? transformBlock(x) : x]; } error:^(NSError *error) { [subscriber sendError:error]; [orderedGroups makeObjectsPerformSelector:@selector(sendError:) withObject:error]; } completed:^{ [subscriber sendCompleted]; [orderedGroups makeObjectsPerformSelector:@selector(sendCompleted)]; }]; }] setNameWithFormat:@"[%@] -groupBy:transform:", self.name]; } |
看groupBy:transform:的實現,依舊是老“套路”。return 一個新的RACSignal,在新的訊號裡面訂閱原訊號。
groupBy:transform:的重點就在subscribeNext中了。
- 首先解釋一下兩個入參。兩個入參都是閉包,keyBlock返回值是要作為字典的key,transformBlock的返回值是對原訊號發出來的值x進行變換。
- 先建立一個NSMutableDictionary字典groups,和NSMutableArray陣列orderedGroups。
- 從字典裡面取出key對應的value,這裡的key對應著keyBlock返回值。value的值是一個RACGroupedSignal訊號。如果找不到對應的key值,就新建一個RACGroupedSignal訊號,並存入字典對應的key值,與之對應。
- 新變換之後的訊號,訂閱之後,RACGroupedSignal進行sendNext,這是一個訊號,如果transformBlock不為空,就傳送transformBlock變換之後的值。
- sendError和sendCompleted都要分別對陣列orderedGroups裡面每個RACGroupedSignal都要進行sendError或者sendCompleted。因為要對陣列裡面每個訊號都執行一個操作,所以需要呼叫makeObjectsPerformSelector:withObject:方法。
經過groupBy:transform:變換之後,原訊號會根據keyBlock進行分組。
寫出測試程式碼,來看看平時應該怎麼用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id subscriber) { [subscriber sendNext:@1]; [subscriber sendNext:@2]; [subscriber sendNext:@3]; [subscriber sendNext:@4]; [subscriber sendNext:@5]; [subscriber sendCompleted]; return [RACDisposable disposableWithBlock:^{ NSLog(@"signal dispose"); }]; }]; RACSignal *signalGroup = [signalA groupBy:^id(NSNumber *object) { return object.integerValue > 3 ? @"good" : @"bad"; } transform:^id(NSNumber * object) { return @(object.integerValue * 10); }]; [[[signalGroup filter:^BOOL(RACGroupedSignal *value) { return [(NSString *)value.key isEqualToString:@"good"]; }] flatten]subscribeNext:^(id x) { NSLog(@"subscribeNext: %@", x); }]; |
假設原訊號傳送的1,2,3,4,5是代表的成績的5個等級。當成績大於3的都算“good”,小於3的都算“bad”。
signalGroup是原訊號signalA經過groupBy:transform:得到的新的訊號,這個訊號是一個高階的訊號,因為它裡面並不是直接裝的是值,signalGroup這個訊號裡面裝的還是訊號。signalGroup裡面有兩個分組,分別是“good”分組和“bad”分組。
想從中取出這兩個分組裡面的值,需要進行一次filter:篩選。篩選之後得到對應分組的高階訊號。這時還要再進行一個flatten操作,把高階訊號變成低階訊號,再次訂閱才能取到其中的值。
訂閱新訊號的值,輸出如下:
1 2 |
subscribeNext: 40 subscribeNext: 50 |
關於flatten的實現:
1 2 3 4 5 6 |
- (instancetype)flatten { __weak RACStream *stream __attribute__((unused)) = self; return [[self flattenMap:^(id value) { return value; }] setNameWithFormat:@"[%@] -flatten", self.name]; } |
flatten操作就是呼叫了flattenMap:把值傳進去了。
1 2 3 4 5 6 7 8 9 10 11 12 |
- (instancetype)flattenMap:(RACStream * (^)(id value))block { Class class = self.class; return [[self bind:^{ return ^(id value, BOOL *stop) { id stream = block(value) ?: [class empty]; NSCAssert([stream isKindOfClass:RACStream.class], @"Value returned from -flattenMap: is not a stream: %@", stream); return stream; }; }] setNameWithFormat:@"[%@] -flattenMap:", self.name]; } |
flatten是把高階訊號變換成低階訊號的常用操作。flattenMap:具體實現上篇文章分析過了,這裡不再贅述。
15. groupBy:
1 2 3 |
- (RACSignal *)groupBy:(id (^)(id object))keyBlock { return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name]; } |
groupBy:操作就是groupBy:transform:的縮減版,transform傳入的為nil。
關於groupBy:可以乾的事情很多,可以進行很高階的分組操作。這裡可以舉一個例子:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 簡單演算法題,分離陣列中的相同的元素,如果元素個數大於2,則組成一個新的陣列,結果得到多個包含相同元素的陣列, // 例如[1,2,3,1,2,3]分離成[1,1],[2,2],[3,3] RACSignal *signal = @[@1, @2, @3, @4,@2,@3,@3,@4,@4,@4].rac_sequence.signal; NSArray * array = [[[[signal groupBy:^NSString *(NSNumber *object) { return [NSString stringWithFormat:@"%@",object]; }] map:^id(RACGroupedSignal *value) { return [value sequence]; }] sequence] map:^id(RACSignalSequence * value) { return value.array; }].array; for (NSNumber * num in array) { NSLog(@"最後的陣列%@",num); } // 最後輸出 [1,2,3,4,2,3,3,4,4,4]變成[1],[2,2],[3,3,3],[4,4,4,4] |
二. 組合操作
1. startWith: (在父類RACStream中定義的)
1 2 3 4 5 6 |
- (instancetype)startWith:(id)value { return [[[self.class return:value] concat:self] setNameWithFormat:@"[%@] -startWith: %@", self.name, [value rac_description]]; } |
startWith:的實現很簡單,就是先構造一個只傳送一個value的訊號,然後這個訊號傳送完畢之後接上原訊號。得到的新的訊號就是在原訊號前面新加了一個值。
2. concat: (在父類RACStream中定義的)
這裡說的concat:是在父類RACStream中定義的。
1 2 3 |
- (instancetype)concat:(RACStream *)stream { return nil; } |
父類中定義的這個方法就返回一個nil,具體的實現還要子類去重寫。
3. concat: (在父類RACStream中定義的)
1 2 3 4 5 6 7 8 |
+ (instancetype)concat:(id)streams { RACStream *result = self.empty; for (RACStream *stream in streams) { result = [result concat:stream]; } return [result setNameWithFormat:@"+concat: %@", streams]; } |
這個concat:後面跟著一個陣列,陣列裡麵包含這很多訊號,concat:依次把這些訊號concat:連線串起來。
4. merge:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
+ (RACSignal *)merge:(id)signals { NSMutableArray *copiedSignals = [[NSMutableArray alloc] init]; for (RACSignal *signal in signals) { [copiedSignals addObject:signal]; } return [[[RACSignal createSignal:^ RACDisposable * (id subscriber) { for (RACSignal *signal in copiedSignals) { [subscriber sendNext:signal]; } [subscriber sendCompleted]; return nil; }] flatten] setNameWithFormat:@"+merge: %@", copiedSignals]; } |
merge:後面跟一個陣列。先會新建一個陣列copiedSignals,把傳入的訊號都裝到陣列裡。然後依次傳送陣列裡面的訊號。由於新訊號也是一個高階訊號,因為sendNext會把各個訊號都依次傳送出去,所以需要flatten操作把這個訊號轉換成值傳送出去。
從上圖上看,上下兩個訊號就像被拍扁了一樣,就成了新訊號的傳送順序。
5. merge:
1 2 3 4 5 |
- (RACSignal *)merge:(RACSignal *)signal { return [[RACSignal merge:@[ self, signal ]] setNameWithFormat:@"[%@] -merge: %@", self.name, signal]; } |
merge:後面引數也可以跟一個訊號,那麼merge:就是合併這兩個訊號。具體實現和merge:多個訊號是一樣的原理。
6. zip: (在父類RACStream中定義的)
1 2 3 4 5 |
+ (instancetype)zip:(id)streams { return [[self join:streams block:^(RACStream *left, RACStream *right) { return [left zipWith:right]; }] setNameWithFormat:@"+zip: %@", streams]; } |
zip:後面可以跟一個陣列,陣列裡面裝的是各種訊號流。
它的實現是呼叫了join: block: 實現的。
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 |
+ (instancetype)join:(id)streams block:(RACStream * (^)(id, id))block { RACStream *current = nil; // 第一步 for (RACStream *stream in streams) { if (current == nil) { current = [stream map:^(id x) { return RACTuplePack(x); }]; continue; } current = block(current, stream); } // 第二步 if (current == nil) return [self empty]; return [current map:^(RACTuple *xs) { NSMutableArray *values = [[NSMutableArray alloc] init]; // 第三步 while (xs != nil) { [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; xs = (xs.count > 1 ? xs.first : nil); } // 第四步 return [RACTuple tupleWithObjectsFromArray:values]; }]; } |
join: block: 的實現可以分為4步:
- 依次打包各個訊號流,把每個訊號流都打包成元組RACTuple。首先第一個訊號流打包成一個元組,這個元組裡面就一個訊號。接著把第一個元組和第二個訊號執行block( )閉包裡面的操作。傳入的block( )閉包執行的是zipWith:的操作。這個操作是把兩個訊號“壓”在一起。具體實現分析請看第一篇文章裡面分析過的,這裡就不再贅述了。得到第二個元組,裡面裝著是第一個元組和第二個訊號。之後每次迴圈都執行類似的操作,再把第二個元組和第三個訊號進行zipWith:操作,以此類推下去,直到所有的訊號流都迴圈一遍。
- 經過第一步的迴圈操作之後,還是nil,那麼肯定就是空訊號了,就返回empty訊號。
- 這一步是把之前第一步打包出來的結果,還原回原訊號的過程。經過第一步的迴圈之後,current會是類似這個樣子,(((1), 2), 3),第三步就是為了把這種多重元組解出來,每個訊號流都依次按照順序放在陣列裡。注意觀察current的特點,最外層的元組,是一個值和一個元組,所以從最外層的元組開始,一層一層往裡“剝”。while迴圈每次都取最外層元組的last,即那個單獨的值,插入到陣列的第0號位置,然後取出first即是裡面一層的元組。然後依次迴圈。由於每次都插入到陣列0號的位置,類似於連結串列的頭插法,最終陣列裡面的順序肯定也保證是原訊號的順序。
- 第四步就是把還原成原訊號的順序的陣列包裝成元組,返回給map操作的閉包。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array { return [self tupleWithObjectsFromArray:array convertNullsToNils:NO]; } + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert { RACTuple *tuple = [[self alloc] init]; if (convert) { NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count]; for (id object in array) { [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)]; } tuple.backingArray = newArray; } else { tuple.backingArray = [array copy]; } return tuple; } |
在轉換過程中,入參convertNullsToNils的含義是,是否把陣列裡面的NSNull轉換成RACTupleNil。
這裡轉換傳入的是NO,所以就是把陣列原封不動的copy一份。
測試程式碼:
1 2 3 4 5 6 7 8 |
RACSignal *signalD = [RACSignal interval:3 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0]; RACSignal *signalO = [RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0]; RACSignal *signalE = [RACSignal interval:4 onScheduler:[RACScheduler mainThreadScheduler] withLeeway:0]; RACSignal *signalB = [RACStream zip:@[signalD,signalO,signalE]]; [signalB subscribeNext:^(id x) { NSLog(@"最後接收到的值 = %@",x); }]; |
列印輸出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
2016-11-29 13:07:57.349 最後接收到的值 = ( "2016-11-29 05:07:56 +0000", "2016-11-29 05:07:54 +0000", "2016-11-29 05:07:57 +0000" ) 2016-11-29 13:08:01.350 最後接收到的值 = ( "2016-11-29 05:07:59 +0000", "2016-11-29 05:07:55 +0000", "2016-11-29 05:08:01 +0000" ) 2016-11-29 13:08:05.352 最後接收到的值 = ( "2016-11-29 05:08:02 +0000", "2016-11-29 05:07:56 +0000", "2016-11-29 05:08:05 +0000" ) |
最後輸出的訊號以時間最長的為主,最後接到的訊號是一個元組,裡面依次包含zip:陣列裡每個訊號在一次“壓”縮週期裡面的值。
7. zip: reduce: (在父類RACStream中定義的)
1 2 3 4 5 6 |
+ (instancetype)zip:(id)streams reduce:(id (^)())reduceBlock { NSCParameterAssert(reduceBlock != nil); RACStream *result = [self zip:streams]; if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; } |
zip: reduce:是一個組合的方法。具體實現可以拆分成兩部分,第一部分是先執行zip:,把陣列裡面的訊號流依次都進行組合。這一過程的實現在上一個變換實現中分析過了。zip:完成之後,緊接著進行reduceEach:操作。
這裡有一個判斷reduceBlock是否為nil的判斷,這個判斷是針對老版本的“歷史遺留問題”。在ReactiveCocoa 2.5之前的版本,是允許reduceBlock傳入nil,這裡為了防止崩潰,所以加上了這個reduceBlock是否為nil的判斷。
1 2 3 4 5 6 7 8 9 |
- (instancetype)reduceEach:(id (^)())reduceBlock { NSCParameterAssert(reduceBlock != nil); __weak RACStream *stream __attribute__((unused)) = self; return [[self map:^(RACTuple *t) { NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t]; }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; } |
reduceEach:操作在上篇已經分析過了。它會動態的構造閉包,對原訊號每個元組,執行reduceBlock( )閉包裡面的方法。具體分析見上篇。一般用法如下:
1 2 3 |
[RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) { return [NSString stringWithFormat:@"%@: %@", string, number]; }]; |
8. zipWith: (在父類RACStream中定義的)
1 2 3 |
- (instancetype)zipWith:(RACStream *)stream { return nil; } |
這個方法就是在父類的RACStream中定義了,具體實現還要看RACStream各個子類的實現。
它就可以類比concat:在父類中的實現,也是直接返回一個nil。
1 |
- (instancetype)concat:(RACStream *)stream { return nil;} |
在第一篇中分析了concat:和zipWith:在RACSignal子類中具體實現。忘記了具體實現的可以回去看看。
9. combineLatestWith:
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 |
- (RACSignal *)combineLatestWith:(RACSignal *)signal { NSCParameterAssert(signal != nil); return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable]; // 初始化第一個訊號的一些標誌變數 __block id lastSelfValue = nil; __block BOOL selfCompleted = NO; // 初始化第二個訊號的一些標誌變數 __block id lastOtherValue = nil; __block BOOL otherCompleted = NO; // 這裡是一個判斷是否sendNext的閉包 void (^sendNext)(void) = ^{ }; // 訂閱第一個訊號 RACDisposable *selfDisposable = [self subscribeNext:^(id x) { }]; [disposable addDisposable:selfDisposable]; // 訂閱第二個訊號 RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { }]; [disposable addDisposable:otherDisposable]; return disposable; }] setNameWithFormat:@"[%@] -combineLatestWith: %@", self.name, signal]; } |
大體實現思路比較簡單,在新訊號裡面分別訂閱原訊號和入參signal訊號。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
RACDisposable *selfDisposable = [self subscribeNext:^(id x) { @synchronized (disposable) { lastSelfValue = x ?: RACTupleNil.tupleNil; sendNext(); } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ @synchronized (disposable) { selfCompleted = YES; if (otherCompleted) [subscriber sendCompleted]; } }]; |
先來看看原訊號訂閱的具體實現:
在subscribeNext閉包中,記錄下原訊號最新傳送的x值,並儲存到lastSelfValue中。從此lastSelfValue變數每次都儲存原訊號傳送過來的最新的值。然後再呼叫sendNext( )閉包。
在completed閉包中,selfCompleted中記錄下原訊號傳送完成。這是還要判斷otherCompleted是否完成,即入參訊號signal是否傳送完成,只有兩者都傳送完成了,組合的新訊號才能算全部傳送完成。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
RACDisposable *otherDisposable = [signal subscribeNext:^(id x) { @synchronized (disposable) { lastOtherValue = x ?: RACTupleNil.tupleNil; sendNext(); } } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ @synchronized (disposable) { otherCompleted = YES; if (selfCompleted) [subscriber sendCompleted]; } }]; |
這是對入參訊號signal的處理實現。和原訊號的處理方式完全一致。現在重點就要看看sendNext( )閉包中都做了些什麼。
1 2 3 4 5 6 |
void (^sendNext)(void) = ^{ @synchronized (disposable) { if (lastSelfValue == nil || lastOtherValue == nil) return; [subscriber sendNext:RACTuplePack(lastSelfValue, lastOtherValue)]; } }; |
在sendNext( )閉包中,如果lastSelfValue 或者 lastOtherValue 其中之一有一個為nil,就return,因為這個時候無法結合在一起。當兩個訊號都有值,那麼就把這兩個訊號的最新的值打包成元組傳送出來。
可以看到,每個訊號每傳送出來一個新的值,都會去找另外一個訊號上一個最新的值進行結合。
這裡可以對比一下類似的zip:操作
zip:操作是會把新來的訊號的值存起來,放在陣列裡,然後另外一個訊號傳送一個值過來就和陣列第0位的值相互結合成新的元組訊號傳送出去,並分別移除陣列裡面第0位的兩個值。zip:能保證每次結合的值都是唯一的,不會一個原訊號的值被多次結合到新的元組訊號中。但是combineLatestWith:是不能保證這一點的,在原訊號或者另外一個訊號新訊號傳送前,每次傳送訊號都會結合當前最新的訊號,這裡就會有反覆結合的情況。
10. combineLatest:
1 2 3 4 5 |
+ (RACSignal *)combineLatest:(id)signals { return [[self join:signals block:^(RACSignal *left, RACSignal *right) { return [left combineLatestWith:right]; }] setNameWithFormat:@"+combineLatest: %@", signals]; } |
combineLatest:的實現就是把入引數組裡面的每個訊號都呼叫一次join: block:方法。傳入的閉包是把兩個訊號combineLatestWith:一下。combineLatest:的實現就是2個操作的組合。具體實現上面也都分析過,這裡不再贅述。
11. combineLatest: reduce:
1 2 3 4 5 6 |
+ (RACSignal *)combineLatest:(id)signals reduce:(id (^)())reduceBlock { NSCParameterAssert(reduceBlock != nil); RACSignal *result = [self combineLatest:signals]; if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; return [result setNameWithFormat:@"+combineLatest: %@ reduce:", signals]; } |
combineLatest: reduce: 的實現可以類比zip: reduce:的實現。
具體實現可以拆分成兩部分,第一部分是先執行combineLatest:,把陣列裡面的訊號流依次都進行組合。這一過程的實現在上一個變換實現中分析過了。combineLatest:完成之後,緊接著進行reduceEach:操作。
這裡有一個判斷reduceBlock是否為nil的判斷,這個判斷是針對老版本的“歷史遺留問題”。在ReactiveCocoa 2.5之前的版本,是允許reduceBlock傳入nil,這裡為了防止崩潰,所以加上了這個reduceBlock是否為nil的判斷。
12. combinePreviousWithStart: reduce:(在父類RACStream中定義的)
這個方法的實現也是多個變換操作組合在一起的。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (instancetype)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { NSCParameterAssert(reduceBlock != NULL); return [[[self scanWithStart:RACTuplePack(start) reduce:^(RACTuple *previousTuple, id next) { id value = reduceBlock(previousTuple[0], next); return RACTuplePack(next, value); }] map:^(RACTuple *tuple) { return tuple[1]; }] setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, [start rac_description]]; } |
combinePreviousWithStart: reduce:的實現完全可以類比scanWithStart:reduce:的實現。舉個例子來說明他們倆的不同。
1 2 3 4 5 6 7 8 9 |
RACSequence *numbers = @[ @1, @2, @3, @4 ].rac_sequence; RACSignal *signalA = [numbers combinePreviousWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) { return @(previous.integerValue + next.integerValue); }].signal; RACSignal *signalB = [numbers scanWithStart:@0 reduce:^(NSNumber *previous, NSNumber *next) { return @(previous.integerValue + next.integerValue); }].signal; |
signalA輸出如下:
1 2 3 4 |
1 3 5 7 |
signalB輸出如下:
1 2 3 4 |
1 3 6 10 |
現在應該不同點應該很明顯了。combinePreviousWithStart: reduce:實現的是兩兩之前的加和,而scanWithStart:reduce:實現的累加。
為什麼會這樣呢,具體看看combinePreviousWithStart: reduce:的實現。
雖然combinePreviousWithStart: reduce:也是呼叫了scanWithStart:reduce:,但是初始值是RACTuplePack(start)元組,聚合reduce的過程也有所不同:
1 2 |
id value = reduceBlock(previousTuple[0], next); return RACTuplePack(next, value); |
依次呼叫reduceBlock( )閉包,傳入previousTuple[0], next,這裡reduceBlock( )閉包是進行累加的操作,所以就是把前一個元組的第0位加上後面新來的訊號的值。得到的值拼成新的元組,新的元組由next和value值構成。
如果列印出上述例子中combinePreviousWithStart: reduce:的加合過程中每個訊號的值,如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
( 1, 1 ) ( 2, 3 ) ( 3, 5 ) ( 4, 7 ) |
由於這樣拆成元組之後,下次再進行操作的時候,還可以拿到前一個訊號的值,這樣就不會形成累加的效果。
13. sample:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
- (RACSignal *)sample:(RACSignal *)sampler { NSCParameterAssert(sampler != nil); return [[RACSignal createSignal:^(id subscriber) { NSLock *lock = [[NSLock alloc] init]; __block id lastValue; __block BOOL hasValue = NO; RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { // 暫時省略 }]; samplerDisposable.disposable = [sampler subscribeNext:^(id _) { // 暫時省略 }]; return [RACDisposable disposableWithBlock:^{ [samplerDisposable dispose]; [sourceDisposable dispose]; }]; }] setNameWithFormat:@"[%@] -sample: %@", self.name, sampler]; } |
sample:內部實現也是對原訊號和入參訊號sampler分別進行訂閱。具體實現就是這兩個訊號訂閱內部都幹了些什麼。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
RACSerialDisposable *samplerDisposable = [[RACSerialDisposable alloc] init]; RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { [lock lock]; hasValue = YES; lastValue = x; [lock unlock]; } error:^(NSError *error) { [samplerDisposable dispose]; [subscriber sendError:error]; } completed:^{ [samplerDisposable dispose]; [subscriber sendCompleted]; }]; |
這是對原訊號的操作,原訊號的操作在subscribeNext中就記錄了兩個變數的值,hasValue記錄原訊號有值,lastValue記錄了原訊號的最新的值。這裡加了一層NSLock鎖進行保護。
在發生error的時候,先把sampler訊號取消訂閱,然後再sendError:。當原訊號完成的時候,同樣是先把sampler訊號取消訂閱,然後再sendCompleted。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
samplerDisposable.disposable = [sampler subscribeNext:^(id _) { BOOL shouldSend = NO; id value; [lock lock]; shouldSend = hasValue; value = lastValue; [lock unlock]; if (shouldSend) { [subscriber sendNext:value]; } } error:^(NSError *error) { [sourceDisposable dispose]; [subscriber sendError:error]; } completed:^{ [sourceDisposable dispose]; [subscriber sendCompleted]; }]; |
這是對入參訊號sampler的操作。shouldSend預設值是NO,這個變數控制著是否sendNext:值。只有當原訊號有值的時候,hasValue = YES,所以shouldSend = YES,這個時候才能傳送原訊號的值。這裡我們並不關心入參訊號sampler的值,從subscribeNext:^(id _)這裡可以看出, _代表並不需要它的值。
在發生error的時候,先把原訊號取消訂閱,然後再sendError:。當sampler訊號完成的時候,同樣是先把原訊號取消訂閱,然後再sendCompleted。
經過sample:變換就會變成這個樣子。只是把原訊號的值都移動到了sampler訊號傳送訊號的時刻,值還是和原訊號的值一樣。
最後
關於RACSignal的變換操作還剩下 冷熱訊號轉換操作,高階訊號操作,下篇接著繼續分析。最後請大家多多指教。
打賞支援我寫出更多好文章,謝謝!
打賞作者
打賞支援我寫出更多好文章,謝謝!
任選一種支付方式