前言
緊接著上篇的源碼實現分析,繼續分析RACSignal的變換操作的底層實現。
目錄
1.過濾操作
2.組合操作
一. 過濾操作
過濾操作也屬于一種變換,根據過濾條件,過濾出符合條件的值。變換出來的新的信號是原信號的一個子集。
- filter: (在父類RACStream中定義的)
這個filter:操作在any:的實現中用到過了。
- (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的。
- ignoreValues
- (RACSignal *)ignoreValues {
return [[self filter:^(id _) {
return NO;
}] setNameWithFormat:@"[%@] -ignoreValues", self.name];
}
由上面filter的實現,這里把篩選判斷條件永遠的傳入NO,那么原信號的值都會被變換成empty信號,故變換之后的信號為空信號。
- ignore: (在父類RACStream中定義的)
- (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:實現的。傳入的篩選判斷條件是一個值,當原信號發送的值中是這個值的時候,就替換成空信號。
- distinctUntilChanged (在父類RACStream中定義的)
- (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信號。效果如上圖。
- take: (在父類RACStream中定義的)
- (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。
// 我自己增加實現的方法
- (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];
}
- takeLast:
- (RACSignal *)takeLast:(NSUInteger)count {
return [[RACSignal createSignal:^(id<RACSubscriber> 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:就一直沒法發出任何信號來。
- takeUntilBlock: (在父類RACStream中定義的)
- (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直到閉包滿足條件。
- takeWhileBlock: (在父類RACStream中定義的)
- (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( )閉包的集合。
- takeUntil:
- (RACSignal *)takeUntil:(RACSignal *)signalTrigger {
return [[RACSignal createSignal:^(id<RACSubscriber> 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( )閉包。
void (^triggerCompletion)(void) = ^{
[disposable dispose];
[subscriber sendCompleted];
};
一旦調用了triggerCompletion( )閉包,就會把原信號取消訂閱,并給變換的新的信號訂閱者sendCompleted。
如果入參signalTrigger一直沒有sendNext,那么原信號就會一直sendNext:。
- takeUntilReplacement:
- (RACSignal *)takeUntilReplacement:(RACSignal *)replacement {
return [RACSignal createSignal:^(id<RACSubscriber> 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信號完成的時刻。
- skip: (在父類RACStream中定義的)
- (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 個信號。
分析清楚后,代碼就很容易了:
// 我自己增加實現的方法
- (RACSignal *)skipLast:(NSUInteger)count {
return [[RACSignal createSignal:^(id<RACSubscriber> 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:的效果了。
- skipUntilBlock: (在父類RACStream中定義的)
- (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了。
- skipWhileBlock: (在父類RACStream中定義的)
- (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方法。
// 我自己增加實現的方法
- (RACSignal *)skipUntil:(RACSignal *)signalTrigger {
return [[RACSignal createSignal:^(id<RACSubscriber> 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信號。所以說這個操作也就沒意義了。
- groupBy:transform:
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock transform:(id (^)(id object))transformBlock {
NSCParameterAssert(keyBlock != NULL);
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
NSMutableDictionary *groups = [NSMutableDictionary dictionary];
NSMutableArray *orderedGroups = [NSMutableArray array];
return [self subscribeNext:^(id x) {
id<NSCopying> 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進行分組。
寫出測試代碼,來看看平時應該怎么用。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> 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<NSCopying>(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操作,把高階信號變成低階信號,再次訂閱才能取到其中的值。
訂閱新信號的值,輸出如下:
subscribeNext: 40
subscribeNext: 50
關于flatten的實現:
- (instancetype)flatten {
__weak RACStream *stream __attribute__((unused)) = self;
return [[self flattenMap:^(id value) {
return value;
}] setNameWithFormat:@"[%@] -flatten", self.name];
}
flatten操作就是調用了flattenMap:把值傳進去了。
- (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:具體實現上篇文章分析過了,這里不再贅述。
- groupBy:
- (RACSignal *)groupBy:(id<NSCopying> (^)(id object))keyBlock {
return [[self groupBy:keyBlock transform:nil] setNameWithFormat:@"[%@] -groupBy:", self.name];
}
groupBy:操作就是groupBy:transform:的縮減版,transform傳入的為nil。
關于groupBy:可以干的事情很多,可以進行很高級的分組操作。這里可以舉一個例子:
// 簡單算法題,分離數組中的相同的元素,如果元素個數大于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中定義的)
-
(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中定義的。
- (instancetype)concat:(RACStream *)stream {
return nil;
}
父類中定義的這個方法就返回一個nil,具體的實現還要子類去重寫。
3. concat: (在父類RACStream中定義的)
-
(instancetype)concat:(id<NSFastEnumeration>)streams {
RACStream *result = self.empty;
for (RACStream *stream in streams) {
result = [result concat:stream];
}return [result setNameWithFormat:@"+concat: %@", streams];
}
這個concat:后面跟著一個數組,數組里面包含這很多信號,concat:依次把這些信號concat:連接串起來。
4. merge:
-
(RACSignal *)merge:(id<NSFastEnumeration>)signals {
NSMutableArray *copiedSignals = [[NSMutableArray alloc] init];
for (RACSignal *signal in signals) {
[copiedSignals addObject:signal];
}return [[[RACSignal
createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
for (RACSignal *signal in copiedSignals) {
[subscriber sendNext:signal];
}[subscriber sendCompleted]; return nil; }] flatten] setNameWithFormat:@"+merge: %@", copiedSignals];
}
merge:后面跟一個數組。先會新建一個數組copiedSignals,把傳入的信號都裝到數組里。然后依次發送數組里面的信號。由于新信號也是一個高階信號,因為sendNext會把各個信號都依次發送出去,所以需要flatten操作把這個信號轉換成值發送出去。
從上圖上看,上下兩個信號就像被拍扁了一樣,就成了新信號的發送順序。
5. merge:
- (RACSignal *)merge:(RACSignal *)signal {
return [[RACSignal
merge:@[ self, signal ]]
setNameWithFormat:@"[%@] -merge: %@", self.name, signal];
}
merge:后面參數也可以跟一個信號,那么merge:就是合并這兩個信號。具體實現和merge:多個信號是一樣的原理。
6. zip: (在父類RACStream中定義的)
- (instancetype)zip:(id<NSFastEnumeration>)streams {
return [[self join:streams block:^(RACStream *left, RACStream *right) {
return [left zipWith:right];
}] setNameWithFormat:@"+zip: %@", streams];
}
zip:后面可以跟一個數組,數組里面裝的是各種信號流。
它的實現是調用了join: block: 實現的。
-
(instancetype)join:(id<NSFastEnumeration>)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操作的閉包。
(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一份。
測試代碼:
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);
}];
打印輸出:
2016-11-29 13:07:57.349 最后接收到的值 = <RACTuple: 0x608000011440> (
"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 最后接收到的值 = <RACTuple: 0x608000010c60> (
"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 最后接收到的值 = <RACTuple: 0x60000001a350> (
"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中定義的)
- (instancetype)zip:(id<NSFastEnumeration>)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的判斷。
-
(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( )閉包里面的方法。具體分析見上篇。一般用法如下:
[RACStream zip:@[ stringSignal, intSignal ] reduce:^(NSString *string, NSNumber *number) {
return [NSString stringWithFormat:@"%@: %@", string, number];
}];
8. zipWith: (在父類RACStream中定義的)
- (instancetype)zipWith:(RACStream *)stream {
return nil;
}
這個方法就是在父類的RACStream中定義了,具體實現還要看RACStream各個子類的實現。
它就可以類比concat:在父類中的實現,也是直接返回一個nil。
- (instancetype)concat:(RACStream *)stream { return nil;}
在第一篇中分析了concat:和zipWith:在RACSignal子類中具體實現。忘記了具體實現的可以回去看看。
9. combineLatestWith:
-
(RACSignal *)combineLatestWith:(RACSignal *)signal {
NSCParameterAssert(signal != nil);return [[RACSignal createSignal:^(id<RACSubscriber> 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信號。
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是否發送完成,只有兩者都發送完成了,組合的新信號才能算全部發送完成。
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( )閉包中都做了些什么。
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:
- (RACSignal *)combineLatest:(id<NSFastEnumeration>)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:
- (RACSignal *)combineLatest:(id<NSFastEnumeration>)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中定義的)
這個方法的實現也是多個變換操作組合在一起的。
- (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:的實現。舉個例子來說明他們倆的不同。
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
3
5
7
signalB輸出如下:
1
3
6
10
現在應該不同點應該很明顯了。combinePreviousWithStart: reduce:實現的是兩兩之前的加和,而scanWithStart:reduce:實現的累加。
為什么會這樣呢,具體看看combinePreviousWithStart: reduce:的實現。
雖然combinePreviousWithStart: reduce:也是調用了scanWithStart:reduce:,但是初始值是RACTuplePack(start)元組,聚合reduce的過程也有所不同:
id value = reduceBlock(previousTuple[0], next);
return RACTuplePack(next, value);
依次調用reduceBlock( )閉包,傳入previousTuple[0], next,這里reduceBlock( )閉包是進行累加的操作,所以就是把前一個元組的第0位加上后面新來的信號的值。得到的值拼成新的元組,新的元組由next和value值構成。
如果打印出上述例子中combinePreviousWithStart: reduce:的加合過程中每個信號的值,如下:
<RACTuple: 0x608000200010> (
1,
1
)
<RACTuple: 0x60000001fe70> (
2,
3
)
<RACTuple: 0x60000001fe90> (
3,
5
)
<RACTuple: 0x60000001feb0> (
4,
7
)
由于這樣拆成元組之后,下次再進行操作的時候,還可以拿到前一個信號的值,這樣就不會形成累加的效果。
13. sample:
-
(RACSignal *)sample:(RACSignal *)sampler {
NSCParameterAssert(sampler != nil);return [[RACSignal createSignal:^(id<RACSubscriber> 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分別進行訂閱。具體實現就是這兩個信號訂閱內部都干了些什么。
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。
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的變換操作還剩下 冷熱信號轉換操作,高階信號操作,下篇接著繼續分析。最后請大家多多指教。
轉載自:https://gold.xitu.io/post/583e096461ff4b007edddbc8