过滤操作也属于一种变换,根据过滤条件,过滤出符合条件的值。变换出来的新的信号是原信号的一个子集。
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中定义的)
Objective-C
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。
Objective-C
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]; }]; }]; } |
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中了。
经过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步:
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信号发送信号的时刻,值还是和原信号的值一样。
高阶操作大部分的操作是针对高阶信号的,也就是说信号里面发送的值还是一个信号或者是一个高阶信号。可以类比数组,这里就是多维数组,数组里面还是套的数组。
1. flattenMap: (在父类RACStream中定义的)
flattenMap:在整个RAC中具有很重要的地位,很多信号变换都是可以用flattenMap:来实现的。
map:,flatten,filter,sequenceMany:这4个操作都是用flattenMap:来实现的。然而其他变换操作实现里面用到map:,flatten,filter又有很多。
回顾一下map:的实现:
1 2 3 4 5 6 7 8 |
- (instancetype)map:(id (^)(id value))block { NSCParameterAssert(block != nil);
Class class = self.class; return [[self flattenMap:^(id value) { return [class return:block(value)]; }] setNameWithFormat:@"[%@] -map:", self.name]; } |
map:的操作其实就是直接原信号进行的 flattenMap:的操作,变换出来的新的信号的值是block(value)。
flatten的实现接下去会具体分析,这里先略过。
filter的实现:
1 2 3 4 5 6 7 8 |
- (instancetype)filter:(BOOL (^)(id value))block { NSCParameterAssert(block != nil);
Class class = self.class; return [[self flattenMap:^ id (id value) { block(value) ? return [class return:value] : return class.empty; }] setNameWithFormat:@"[%@] -filter:", self.name]; } |
filter的实现和map:有点类似,也是对原信号进行 flattenMap:的操作,只不过block(value)不是作为返回值,而是作为判断条件,满足这个闭包的条件,变换出来的新的信号返回值就是value,不满足的就返回empty信号
接下去要分析的高阶操作里面,switchToLatest,try:,tryMap:的实现中也将会使用到flattenMap:。
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]; } |
flattenMap:的实现是调用了bind函数,对原信号进行变换,并返回block(value)的新信号。关于bind操作的具体流程这篇文章里面已经分析过了,这里不再赘述。
从flattenMap:的源码可以看到,它是可以支持类似Promise的串行异步操作的,并且flattenMap:是满足Monad中bind部分定义的。flattenMap:没法去实现takeUntil:和take:的操作。
然而,bind操作可以实现take:的操作,bind是完全满足Monad中bind部分定义的。
2. flatten (在父类RACStream中定义的)
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操作必须是对高阶信号进行操作,如果信号里面不是信号,即不是高阶信号,那么就会崩溃。崩溃信息如下:
1 |
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Value returned from -flattenMap: is not a stream |
所以flatten是对高阶信号进行的降阶操作。高阶信号每发送一次信号,经过flatten变换,由于flattenMap:操作之后,返回的新的信号的每个值就是原信号中每个信号的值。
如果对信号A,信号B,信号C进行merge:操作,可以达到和flatten一样的效果。
1 |
[RACSignal merge:@[signalA,signalB,signalC]]; |
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]; } |
现在在回来看这段代码,copiedSignals虽然是一个NSMutableArray,但是它近似合成了一个上图中的高阶信号。然后这些信号们每发送出来一个信号就发给订阅者。整个操作如flatten的字面意思一样,压平。
另外,在ReactiveCocoa v2.5中,flatten默认就是flattenMap:这一种操作。
1 2 3 4 5 6 7 8 9 10 11 12 |
public func flatten(_ strategy: FlattenStrategy) -> Signal { switch strategy { case .merge: return self.merge()
case .concat: return self.concat()
case .latest: return self.switchToLatest() } } |
而在ReactiveCocoa v3.x,v4.x,v5.x中,flatten的操作是可以选择3种操作选择的。merge,concat,switchToLatest。
3. flatten:
flatten:操作也必须是对高阶信号进行操作,如果信号里面不是信号,即不是高阶信号,那么就会崩溃。
flatten:的实现比较复杂,一步步的来分析:
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 |
- (RACSignal *)flatten:(NSUInteger)maxConcurrent { return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *compoundDisposable = [[RACCompoundDisposable alloc] init]; NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent]; NSMutableArray *queuedSignals = [NSMutableArray array];
__block BOOL selfCompleted = NO; __block void (^subscribeToSignal)(RACSignal *); __weak __block void (^recur)(RACSignal *); recur = subscribeToSignal = ^(RACSignal *signal) { // 暂时省略};
void (^completeIfAllowed)(void) = ^{ // 暂时省略};
[compoundDisposable addDisposable:[self subscribeNext:^(RACSignal *signal) { if (signal == nil) return;
NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);
@synchronized (subscriber) { if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) { [queuedSignals addObject:signal]; return; } }
subscribeToSignal(signal); } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ @synchronized (subscriber) { selfCompleted = YES; completeIfAllowed(); } }]];
return compoundDisposable; }] setNameWithFormat:@"[%@] -flatten: %lu", self.name, (unsigned long)maxConcurrent]; } |
先来解释一些变量,数组的作用
activeDisposables里面装的是当前正在订阅的订阅者们的disposables信号。
queuedSignals里面装的是被暂时缓存起来的信号,它们等待被订阅。
selfCompleted表示高阶信号是否Completed。
subscribeToSignal闭包的作用是订阅所给的信号。这个闭包的入参参数就是一个信号,在闭包内部订阅这个信号,并进行一些操作。
recur是对subscribeToSignal闭包的一个弱引用,防止strong-weak循环引用,在下面会分析subscribeToSignal闭包,就会明白为什么recur要用weak修饰了。
completeIfAllowed的作用是在所有信号都发送完毕的时候,通知订阅者,给订阅者发送completed。
入参maxConcurrent的意思是最大可容纳同时被订阅的信号个数。
再来详细分析一下具体订阅的过程。
flatten:的内部,订阅高阶信号发出来的信号,这部分的代码比较简单:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
[self subscribeNext:^(RACSignal *signal) { if (signal == nil) return;
NSCAssert([signal isKindOfClass:RACSignal.class], @"Expected a RACSignal, got %@", signal);
@synchronized (subscriber) { // 1 if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) { [queuedSignals addObject:signal]; return; } } // 2 subscribeToSignal(signal); } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ @synchronized (subscriber) { selfCompleted = YES; // 3 completeIfAllowed(); } }]]; |
1 2 3 4 5 6 |
void (^completeIfAllowed)(void) = ^{ if (selfCompleted && activeDisposables.count == 0) { [subscriber sendCompleted]; subscribeToSignal = nil; } }; |
当selfCompleted = YES 并且activeDisposables数组里面的信号都发送完毕,没有可以发送的信号了,即activeDisposables.count = 0,那么就给订阅者sendCompleted。这里值得一提的是,还需要把subscribeToSignal手动置为nil。因为在subscribeToSignal闭包中强引用了completeIfAllowed闭包,防止completeIfAllowed闭包被提早的销毁掉了。所以在completeIfAllowed闭包执行完毕的时候,需要再把subscribeToSignal闭包置为nil。
那么接下来需要看的重点就是subscribeToSignal( )闭包。
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 |
recur = subscribeToSignal = ^(RACSignal *signal) { RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init]; // 1 @synchronized (subscriber) { [compoundDisposable addDisposable:serialDisposable]; [activeDisposables addObject:serialDisposable]; }
serialDisposable.disposable = [signal subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ // 2 __strong void (^subscribeToSignal)(RACSignal *) = recur; RACSignal *nextSignal; // 3 @synchronized (subscriber) { [compoundDisposable removeDisposable:serialDisposable]; [activeDisposables removeObjectIdenticalTo:serialDisposable]; // 4 if (queuedSignals.count == 0) { completeIfAllowed(); return; } // 5 nextSignal = queuedSignals[0]; [queuedSignals removeObjectAtIndex:0]; } // 6 subscribeToSignal(nextSignal); }]; }; |
总结一下:高阶信号每发送一个信号值,判断activeDisposables数组装的个数是否已经超过了maxConcurrent。如果装不下了就缓存进queuedSignals数组中。如果还可以装的下就开始调用subscribeToSignal( )闭包,订阅当前信号。
每发送完一个信号就判断缓存数组queuedSignals的个数,如果缓存数组里面已经没有信号了,那么就结束原来高阶信号的发送。如果缓存数组里面还有信号就继续订阅。如此循环,直到原高阶信号所有的信号都发送完毕。
整个flatten:的执行流程都分析清楚了,最后,关于入参maxConcurrent进行更进一步的解读。
回看上面flatten:的实现中有这样一句话:
1 |
if (maxConcurrent > 0 && activeDisposables.count >= maxConcurrent) |
那么maxConcurrent的值域就是最终决定flatten:表现行为。
如果maxConcurrent
1 |
NSMutableArray *activeDisposables = [[NSMutableArray alloc] initWithCapacity:maxConcurrent]; |
activeDisposables在初始化的时候会初始化一个大小为maxConcurrent的NSMutableArray。如果maxConcurrent
如果maxConcurrent = 0,会发生什么?那么flatten:就退化成flatten了。
如果maxConcurrent = 1,会发生什么?那么flatten:就退化成concat了。
如果maxConcurrent > 1,会发生什么?由于至今还没有遇到能用到maxConcurrent > 1的需求情况,所以这里暂时不展示图解了。maxConcurrent > 1之后,flatten的行为还依照高阶信号的个数和maxConcurrent的关系。如果高阶信号的个数maxConcurrent的值,那么多的信号就会进入queuedSignals缓存数组。
4. concat
这里的concat实现是在RACSignal里面定义的。
1 2 3 |
- (RACSignal *)concat { return [[self flatten:1] setNameWithFormat:@"[%@] -concat", self.name]; } |
一看源码就知道了,concat其实就是flatten:1。
当然在RACSignal中定义了concat:方法,这个方法在之前的文章已经分析过了,这里回顾对比一下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (RACSignal *)concat:(RACSignal *)signal { return [[RACSignal createSignal:^(id subscriber) { RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];
RACDisposable *sourceDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ RACDisposable *concattedDisposable = [signal subscribe:subscriber]; serialDisposable.disposable = concattedDisposable; }];
serialDisposable.disposable = sourceDisposable; return serialDisposable; }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal]; } |
经过对比可以发现,虽然最终变换出来的结果类似,但是针对的信号的对象是不同的,concat是针对高阶信号进行降阶操作。concat:是把两个信号连接起来的操作。如果把高阶信号按照时间轴,从左往右,依次把每个信号都concat:连接起来,那么结果就是concat。
5. switchToLatest
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (RACSignal *)switchToLatest { return [[RACSignal createSignal:^(id subscriber) { RACMulticastConnection *connection = [self publish];
RACDisposable *subscriptionDisposable = [[connection.signal flattenMap:^(RACSignal *x) { NSCAssert(x == nil || [x isKindOfClass:RACSignal.class], @"-switchToLatest requires that the source signal (%@) send signals. Instead we got: %@", self, x); return [x takeUntil:[connection.signal concat:[RACSignal never]]]; }] subscribe:subscriber];
RACDisposable *connectionDisposable = [connection connect]; return [RACDisposable disposableWithBlock:^{ [subscriptionDisposable dispose]; [connectionDisposable dispose]; }]; }] setNameWithFormat:@"[%@] -switchToLatest", self.name]; } |
switchToLatest这个操作只能用在高阶信号上,如果原信号里面有不是信号的值,那么就会崩溃,崩溃信息如下:
1 |
***** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-switchToLatest requires that the source signal ( name: ) send signals. |
在switchToLatest操作中,先把原信号转换成热信号,connection.signal就是RACSubject类型的。对RACSubject进行flattenMap:变换。在flattenMap:变换中,connection.signal会先concat:一个never信号。这里concat:一个never信号的原因是为了内部的信号过早的结束而导致订阅者收到complete信号。
flattenMap:变换中x也是一个信号,对x进行takeUntil:变换,效果就是下一个信号到来之前,x会一直发送信号,一旦下一个信号到来,x就会被取消订阅,开始订阅新的信号。
一个高阶信号经过switchToLatest降阶操作之后,能得到上图中的信号。
6. switch: cases: default:
switch: cases: default:源码实现如下:
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 |
+ (RACSignal *)switch:(RACSignal *)signal cases:(NSDictionary *)cases default:(RACSignal *)defaultSignal { NSCParameterAssert(signal != nil); NSCParameterAssert(cases != nil);
for (id key in cases) { id value __attribute__((unused)) = cases[key]; NSCAssert([value isKindOfClass:RACSignal.class], @"Expected all cases to be RACSignals, %@ isn't", value); }
NSDictionary *copy = [cases copy];
return [[[signal map:^(id key) { if (key == nil) key = RACTupleNil.tupleNil;
RACSignal *signal = copy[key] ?: defaultSignal; if (signal == nil) { NSString *description = [NSString stringWithFormat:NSLocalizedString(@"No matching signal found for value %@", @""), key]; return [RACSignal error:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorNoMatchingCase userInfo:@{ NSLocalizedDescriptionKey: description }]]; }
return signal; }] switchToLatest] setNameWithFormat:@"+switch: %@ cases: %@ default: %@", signal, cases, defaultSignal]; } |
实现中有3个断言,全部都是针对入参的要求。入参signal信号和cases字典都不能是nil。其次,cases字典里面所有key对应的value必须是RACSignal类型的。注意,defaultSignal是可以为nil的。
接下来的实现比较简单,对入参传进来的signal信号进行map变换,这里的变换是升阶的变换。
signal每次发送出来的一个值,就把这个值当做key值去cases字典里面去查找对应的value。当然value对应的是一个信号。如果value对应的信号不为空,就把signal发送出来的这个值map成字典里面对应的信号。如果value对应为空,那么就把原signal发出来的值map成defaultSignal信号。
如果经过转换之后,得到的信号为nil,就会返回一个error信号。如果得到的信号不为nil,那么原信号完全转换完成就会变成一个高阶信号,这个高阶信号里面装的都是信号。最后再对这个高阶信号执行switchToLatest转换。
7. if: then: else:
if: then: else:源码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
+ (RACSignal *)if:(RACSignal *)boolSignal then:(RACSignal *)trueSignal else:(RACSignal *)falseSignal { NSCParameterAssert(boolSignal != nil); NSCParameterAssert(trueSignal != nil); NSCParameterAssert(falseSignal != nil);
return [[[boolSignal map:^(NSNumber *value) { NSCAssert([value isKindOfClass:NSNumber.class], @"Expected %@ to send BOOLs, not %@", boolSignal, value);
return (value.boolValue ? trueSignal : falseSignal); }] switchToLatest] setNameWithFormat:@"+if: %@ then: %@ else: %@", boolSignal, trueSignal, falseSignal]; } |
入参boolSignal,trueSignal,falseSignal三个信号都不能为nil。
boolSignal里面都必须装的是NSNumber类型的值。
针对boolSignal进行map升阶操作,boolSignal信号里面的值如果是YES,那么就转换成trueSignal信号,如果为NO,就转换成falseSignal。升阶转换完成之后,boolSignal就是一个高阶信号,然后再进行switchToLatest操作。
8. catch:
catch:的实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
- (RACSignal *)catch:(RACSignal * (^)(NSError *error))catchBlock { NSCParameterAssert(catchBlock != NULL);
return [[RACSignal createSignal:^(id subscriber) { RACSerialDisposable *catchDisposable = [[RACSerialDisposable alloc] init];
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { RACSignal *signal = catchBlock(error); NSCAssert(signal != nil, @"Expected non-nil signal from catch block on %@", self); catchDisposable.disposable = [signal subscribe:subscriber]; } completed:^{ [subscriber sendCompleted]; }];
return [RACDisposable disposableWithBlock:^{ [catchDisposable dispose]; [subscriptionDisposable dispose]; }]; }] setNameWithFormat:@"[%@] -catch:", self.name]; } |
当对原信号进行订阅的时候,如果出现了错误,会去执行catchBlock( )闭包,入参为刚刚产生的error。catchBlock( )闭包产生的是一个新的RACSignal,并再次用订阅者订阅该信号。
这里之所以说是高阶操作,是因为这里原信号发生错误之后,错误会升阶成一个信号。
9. catchTo:
catchTo:的实现如下:
1 2 3 4 5 |
- (RACSignal *)catchTo:(RACSignal *)signal { return [[self catch:^(NSError *error) { return signal; }] setNameWithFormat:@"[%@] -catchTo: %@", self.name, signal]; } |
catchTo:的实现就是调用catch:方法,只不过原来catch:方法里面的catchBlock( )闭包,永远都只返回catchTo:的入参,signal信号。
10. try:
1 2 3 4 5 6 7 8 9 |
- (RACSignal *)try:(BOOL (^)(id value, NSError **errorPtr))tryBlock { NSCParameterAssert(tryBlock != NULL);
return [[self flattenMap:^(id value) { NSError *error = nil; BOOL passed = tryBlock(value, &error); return (passed ? [RACSignal return:value] : [RACSignal error:error]); }] setNameWithFormat:@"[%@] -try:", self.name]; } |
try:也是一个高阶操作。对原信号进行flattenMap变换,对信号发出来的每个值都调用一遍tryBlock( )闭包,如果这个闭包的返回值是YES,那么就返回[RACSignal return:value],如果闭包的返回值是NO,那么就返回error。原信号中如果都是值,那么经过try:操作之后,每个值都会变成RACSignal,于是原信号也就变成了高阶信号了。
11. tryMap:
1 2 3 4 5 6 7 8 9 |
- (RACSignal *)tryMap:(id (^)(id value, NSError **errorPtr))mapBlock { NSCParameterAssert(mapBlock != NULL);
return [[self flattenMap:^(id value) { NSError *error = nil; id mappedValue = mapBlock(value, &error); return (mappedValue == nil ? [RACSignal error:error] : [RACSignal return:mappedValue]); }] setNameWithFormat:@"[%@] -tryMap:", self.name]; } |
tryMap:的实现和try:的实现基本一致,唯一不同的就是入参闭包的返回值不同。在tryMap:中调用mapBlock( )闭包,返回是一个对象,如果这个对象不为nil,就返回[RACSignal return:mappedValue]。如果返回的对象是nil,那么就变换成error信号。
12. timeout: onScheduler:
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 *)timeout:(NSTimeInterval)interval onScheduler:(RACScheduler *)scheduler { NSCParameterAssert(scheduler != nil); NSCParameterAssert(scheduler != RACScheduler.immediateScheduler);
return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *timeoutDisposable = [scheduler afterDelay:interval schedule:^{ [disposable dispose]; [subscriber sendError:[NSError errorWithDomain:RACSignalErrorDomain code:RACSignalErrorTimedOut userInfo:nil]]; }];
[disposable addDisposable:timeoutDisposable];
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [disposable dispose]; [subscriber sendError:error]; } completed:^{ [disposable dispose]; [subscriber sendCompleted]; }];
[disposable addDisposable:subscriptionDisposable]; return disposable; }] setNameWithFormat:@"[%@] -timeout: %f onScheduler: %@", self.name, (double)interval, scheduler]; } |
timeout: onScheduler:的实现很简单,它比正常的信号订阅多了一个timeoutDisposable操作。它在信号订阅的内部开启了一个scheduler,经过interval的时间之后,就会停止订阅原信号,并对订阅者sendError。
这个操作的表意和方法名完全一致,经过interval的时间之后,就算timeout,那么就停止订阅原信号,并sendError。
总结一下ReactiveCocoa v2.5中高阶信号的升阶 / 降阶操作:
升阶操作:
降阶操作:
这5种操作能将高阶信号变为低阶信号,但是最终降阶之后的效果就只有3种:switchToLatest,flatten,concat。具体的图示见上面的分析。
在ReactiveCocoa中还包含一些同步的操作,这些操作一般我们很少使用,除非真的很确定这样做了之后不会有什么问题,否则胡乱使用会导致线程死锁等一些严重的问题。
1. firstOrDefault: success: error:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
- (id)firstOrDefault:(id)defaultValue success:(BOOL *)success error:(NSError **)error { NSCondition *condition = [[NSCondition alloc] init]; condition.name = [NSString stringWithFormat:@"[%@] -firstOrDefault: %@ success:error:", self.name, defaultValue];
__block id value = defaultValue; __block BOOL done = NO;
// Ensures that we don't pass values across thread boundaries by reference. __block NSError *localError; __block BOOL localSuccess;
[[self take:1] subscribeNext:^(id x) { // 加锁 [condition lock];
value = x; localSuccess = YES;
done = YES; [condition broadcast]; // 解锁 [condition unlock]; } error:^(NSError *e) { // 加锁 [condition lock];
if (!done) { localSuccess = NO; localError = e;
done = YES; [condition broadcast]; } // 解锁 [condition unlock]; } completed:^{ // 加锁 [condition lock];
localSuccess = YES;
done = YES; [condition broadcast]; // 解锁 [condition unlock]; }]; // 加锁 [condition lock]; while (!done) { [condition wait]; }
if (success != NULL) *success = localSuccess; if (error != NULL) *error = localError; // 解锁 [condition unlock]; return value; } |
从源码上看,firstOrDefault: success: error:这种同步的方法很容易导致线程死锁。它在subscribeNext,error,completed的闭包里面都调用condition锁先lock再unlock。如果一个信号发送值过来,都没有执行subscribeNext,error,completed这3个操作里面的任意一个,那么就会执行[condition wait],等待。
由于对原信号进行了take:1操作,所以只会对第一个值进行操作。执行完subscribeNext,error,completed这3个操作里面的任意一个,又会加一次锁,对外部传进来的入参success和error进行赋值,已便外部可以拿到里面的状态。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是defaultValue。
done为YES表示已经成功执行了subscribeNext,error,completed这3个操作里面的任意一个。反之为NO。
localSuccess为YES表示成功发送值或者成功发送完了原信号的所有值,期间没有发生错误。
condition的broadcast操作是唤醒其他线程的操作,相当于操作系统里面互斥信号量的signal操作。
入参defaultValue是给内部变量value的一个初始值。当原信号发送出一个值之后,value的值时刻都会与原信号的值保持一致。
success和error是外部变量的地址,从外面可以监听到里面的状态。在函数内部赋值,在函数外面拿到它们的值。
2. firstOrDefault:
1 2 3 |
- (id)firstOrDefault:(id)defaultValue { return [self firstOrDefault:defaultValue success:NULL error:NULL]; } |
firstOrDefault:的实现就是调用了firstOrDefault: success: error:方法。只不过不需要传success和error,不关心内部的状态。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是defaultValue。
3. first
1 2 3 |
- (id)first { return [self firstOrDefault:nil]; } |
first方法就更加省略,连defaultValue也不传。最终返回信号是原信号中第一个next里面的值,如果原信号第一个值没有,比如直接error或者completed,那么返回的是nil。
4. waitUntilCompleted:
1 2 3 4 5 6 7 8 9 10 |
- (BOOL)waitUntilCompleted:(NSError **)error { BOOL success = NO;
[[[self ignoreValues] setNameWithFormat:@"[%@] -waitUntilCompleted:", self.name] firstOrDefault:nil success:&success error:error];
return success; } |
waitUntilCompleted:里面还是调用firstOrDefault: success: error:方法。返回值是success。只要原信号正常的发送完信号,success应该为YES,但是如果发送过程中出现了error,success就为NO。success作为返回值,外部就可以监听到是否发送成功。
虽然这个方法可以监听到发送结束的状态,但是也尽量不要使用,因为它的实现调用了firstOrDefault: success: error:方法,这个方法里面有大量的锁的操作,一不留神就会导致死锁。
5. toArray
1 2 3 |
- (NSArray *)toArray { return [[[self collect] first] copy]; } |
经过collect之后,原信号所有的值都会被加到一个数组里面,取出信号的第一个值就是一个数组。所以执行完first之后第一个值就是原信号所有值的数组。
ReactiveCocoa v2.5中还为我们提供了一些可以进行副作用操作的函数。
1. doNext:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (RACSignal *)doNext:(void (^)(id x))block { NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) { return [self subscribeNext:^(id x) { block(x); [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -doNext:", self.name]; } |
doNext:能让我们在原信号sendNext之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。
2. doError:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (RACSignal *)doError:(void (^)(NSError *error))block { NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) { return [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { block(error); [subscriber sendError:error]; } completed:^{ [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -doError:", self.name]; } |
doError:能让我们在原信号sendError之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。
3. doCompleted:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (RACSignal *)doCompleted:(void (^)(void))block { NSCParameterAssert(block != NULL);
return [[RACSignal createSignal:^(id subscriber) { return [self subscribeNext:^(id x) { [subscriber sendNext:x]; } error:^(NSError *error) { [subscriber sendError:error]; } completed:^{ block(); [subscriber sendCompleted]; }]; }] setNameWithFormat:@"[%@] -doCompleted:", self.name]; } |
doCompleted:能让我们在原信号sendCompleted之前,能执行一个block闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。
4. initially:
1 2 3 4 5 6 7 8 |
- (RACSignal *)initially:(void (^)(void))block { NSCParameterAssert(block != NULL);
return [[RACSignal defer:^{ block(); return self; }] setNameWithFormat:@"[%@] -initially:", self.name]; } |
initially:能让我们在原信号发送之前,先调用了defer:操作,在return self之前先执行了一个闭包,在这个闭包中我们可以执行我们想要执行的副作用操作。
5. finally:
1 2 3 4 5 6 7 8 9 10 11 12 |
- (RACSignal *)finally:(void (^)(void))block { NSCParameterAssert(block != NULL);
return [[[self doError:^(NSError *error) { block(); }] doCompleted:^{ block(); }] setNameWithFormat:@"[%@] -finally:", self.name]; } |
finally:操作调用了doError:和doCompleted:操作,依次在sendError之前,sendCompleted之前,插入一个block( )闭包。这样当信号因为错误而要终止取消订阅,或者,发送结束之前,都能执行一段我们想要执行的副作用操作。
在RACSignal里面有3个关于多线程的操作。
1. deliverOn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (RACSignal *)deliverOn:(RACScheduler *)scheduler { return [[RACSignal createSignal:^(id subscriber) { return [self subscribeNext:^(id x) { [scheduler schedule:^{ [subscriber sendNext:x]; }]; } error:^(NSError *error) { [scheduler schedule:^{ [subscriber sendError:error]; }]; } completed:^{ [scheduler schedule:^{ [subscriber sendCompleted]; }]; }]; }] setNameWithFormat:@"[%@] -deliverOn: %@", self.name, scheduler]; } |
deliverOn:的入参是一个scheduler,当原信号subscribeNext,sendError,sendCompleted的时候,都去调用scheduler的schedule方法。
1 2 3 4 5 6 7 8 |
- (RACDisposable *)schedule:(void (^)(void))block { NSCParameterAssert(block != NULL);
if (RACScheduler.currentScheduler == nil) return [self.backgroundScheduler schedule:block];
block(); return nil; } |
在schedule的方法里面会判断当前currentScheduler是否为nil,如果是nil就调用backgroundScheduler去执行block( )闭包,如果不为nil,当前currentScheduler直接执行block( )闭包。
1 2 3 4 5 6 7 |
+ (instancetype)currentScheduler { RACScheduler *scheduler = NSThread.currentThread.threadDictionary[RACSchedulerCurrentSchedulerKey]; if (scheduler != nil) return scheduler; if ([self.class isOnMainThread]) return RACScheduler.mainThreadScheduler;
return nil; } |
判断currentScheduler是否存在,看两点,一是当前线程的字典里面,是否存在RACSchedulerCurrentSchedulerKey( @”RACSchedulerCurrentSchedulerKey” ),如果存在对应的value,返回scheduler,二是看当前的类是不是在主线程,如果在主线程,返回mainThreadScheduler。如果两个条件都不存在,那么当前currentScheduler就不存在,返回nil。
deliverOn:操作的特点是原信号发送sendNext,sendError,sendCompleted所在线程是确定的。
2. subscribeOn:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
- (RACSignal *)subscribeOn:(RACScheduler *)scheduler { return [[RACSignal createSignal:^(id subscriber) { RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
RACDisposable *schedulingDisposable = [scheduler schedule:^{ RACDisposable *subscriptionDisposable = [self subscribe:subscriber];
[disposable addDisposable:subscriptionDisposable]; }];
[disposable addDisposable:schedulingDisposable]; return disposable; }] setNameWithFormat:@"[%@] -subscribeOn: %@", self.name, scheduler]; } |
subscribeOn:操作就是在传入的scheduler的闭包内部订阅原信号的。它与deliverOn:操作就不同:
subscribeOn:操作能够保证didSubscribe block( )闭包在入参scheduler中执行,但是不能保证原信号subscribeNext,sendError,sendCompleted在哪个scheduler中执行。
deliverOn:与subscribeOn:正好反过来,能保证原信号subscribeNext,sendError,sendCompleted在哪个scheduler中执行,但是不能保证didSubscribe block( )闭包在哪个scheduler中执行。
3. deliverOnMainThread
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (RACSignal *)deliverOnMainThread { return [[RACSignal createSignal:^(id subscriber) { __block volatile int32_t queueLength = 0;
void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { // 暂时省略};
return [self subscribeNext:^(id x) { performOnMainThread(^{ [subscriber sendNext:x]; }); } error:^(NSError *error) { performOnMainThread(^{ [subscriber sendError:error]; }); } completed:^{ performOnMainThread(^{ [subscriber sendCompleted]; }); }]; }] setNameWithFormat:@"[%@] -deliverOnMainThread", self.name]; } |
对比deliverOn:的源码实现,发现两者比较相似,只不过这里deliverOnMainThread把sendNext,sendError,sendCompleted都包在了performOnMainThread闭包中执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
__block volatile int32_t queueLength = 0;
void (^performOnMainThread)(dispatch_block_t) = ^(dispatch_block_t block) { int32_t queued = OSAtomicIncrement32(&queueLength); if (NSThread.isMainThread && queued == 1) { block(); OSAtomicDecrement32(&queueLength); } else { dispatch_async(dispatch_get_main_queue(), ^{ block(); OSAtomicDecrement32(&queueLength); }); } }; |
performOnMainThread闭包内部保证了入参block( )闭包一定是在主线程中执行。
OSAtomicIncrement32 和 OSAtomicDecrement32是原子操作,分别代表+1和-1。下面的if-else判断里面,不管是满足哪一条,最终都还是在主线程中执行block( )闭包。
deliverOnMainThread能保证原信号subscribeNext,sendError,sendCompleted都在主线程MainThread中执行。
1. setKeyPath: onObject: nilValue:
setKeyPath: onObject: nilValue: 的源码实现如下:
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 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 |
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object nilValue:(id)nilValue { NSCParameterAssert(keyPath != nil); NSCParameterAssert(object != nil);
keyPath = [keyPath copy];
RACCompoundDisposable *disposable = [RACCompoundDisposable compoundDisposable];
__block void * volatile objectPtr = (__bridge void *)object;
RACDisposable *subscriptionDisposable = [self subscribeNext:^(id x) { // 1 __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr; [object setValue:x ?: nilValue forKeyPath:keyPath]; } error:^(NSError *error) { __strong NSObject *object __attribute__((objc_precise_lifetime)) = (__bridge __strong id)objectPtr;
NSCAssert(NO, @"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error); NSLog(@"Received error from %@ in binding for key path \"%@\" on %@: %@", self, keyPath, object, error);
[disposable dispose]; } completed:^{ [disposable dispose]; }];
[disposable addDisposable:subscriptionDisposable];
#if DEBUG static void *bindingsKey = &bindingsKey; NSMutableDictionary *bindings;
@synchronized (object) { // 2 bindings = objc_getAssociatedObject(object, bindingsKey); if (bindings == nil) { bindings = [NSMutableDictionary dictionary]; objc_setAssociatedObject(object, bindingsKey, bindings, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } }
@synchronized (bindings) { NSCAssert(bindings[keyPath] == nil, @"Signal %@ is already bound to key path \"%@\" on object %@, adding signal %@ is undefined behavior", [bindings[keyPath] nonretainedObjectValue], keyPath, object, self);
bindings[keyPath] = [NSValue valueWithNonretainedObject:self]; } #endif
RACDisposable *clearPointerDisposable = [RACDisposable disposableWithBlock:^{ #if DEBUG @synchronized (bindings) { // 3 [bindings removeObjectForKey:keyPath]; } #endif
while (YES) { void *ptr = objectPtr; // 4 if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) { break; } } }];
[disposable addDisposable:clearPointerDisposable];
[object.rac_deallocDisposable addDisposable:disposable];
RACCompoundDisposable *objectDisposable = object.rac_deallocDisposable; return [RACDisposable disposableWithBlock:^{ [objectDisposable removeDisposable:disposable]; [disposable dispose]; }]; } |
代码虽然有点长,但是逐行读下来不是很难,需要注意的有4点地方,已经在上述代码里面标明了。接下来一一分析。
1. objc_precise_lifetime的问题。
作者在这里写了一段注释:
Possibly spec, possibly compiler bug, but this __bridge cast does not result in a retain here, effectively an invisible __unsafe_unretained qualifier. Using objc_precise_lifetime gives the __strong reference desired. The explicit use of __strong is strictly defensive.
作者怀疑是编译器的一个bug,即使是显示的调用了__strong,依旧没法保证被强引用了,所以还需要用objc_precise_lifetime来保证强引用。
关于这个问题,笔者查询了一下LLVM的文档,在6.3 precise lifetime semantics这一节中提到了这个问题。
通常上,凡是声明了__strong的变量,都会有很确切的生命周期。ARC会维持这些__strong的变量在其生命周期中被retained。
但是自动存储的局部变量是没有确切的生命周期的。这些变量仅仅只是简单的持有一个强引用,强引用着retain对象的指针类型的值。这些值完全受控于本地控制者的如何优化。所以要想改变这些局部变量的生命周期,是不可能的事情。因为有太多的优化,理论上都会导致局部变量的生命周期减少,但是这些优化非常有用。
但是LLVM为我们提供了一个关键字objc_precise_lifetime,使用这个可以是局部变量的生命周期变成确切的。这个关键字有时候还是非常有用的。甚至更加极端情况,该局部变量都没有被使用,但是它依旧可以保持一个确定的生命周期。
回到源码上来,接着代码会对入参object进行setValue: forKeyPath:
1 |
[object setValue:x ?: nilValue forKeyPath:keyPath]; |
如何x为nil就返回nilValue传进来的值。
2. AssociatedObject关联对象
如果bindings字典不存在,那么就调用objc_setAssociatedObject对object进行关联对象。参数是OBJC_ASSOCIATION_RETAIN_NONATOMIC。如果bindings字典存在,就用objc_getAssociatedObject取出字典。
在字典里面重新更新绑定key-value值,key就是入参keyPath,value是原信号。
3. 取消订阅原信号的时候
1 |
[bindings removeObjectForKey:keyPath]; |
当信号取消订阅的时候,移除所有的关联值。
3. OSAtomicCompareAndSwapPtrBarrier
这个函数属于OSAtomic原子操作,原型如下:
1 |
OSAtomicCompareAndSwapPtrBarrier(type __oldValue, type __newValue, volatile type *__theValue) |
Compares a variable against the specified old value. If the two values are equal, this function assigns the specified new value to the variable; otherwise, it does nothing. The comparison and assignment are done as one atomic operation and the function returns a Boolean value indicating whether the swap actually occurred.
这个函数用于比较__oldValue是否与__theValue指针指向的内存位置的值匹配,如果匹配,则将__newValue的值存储到__theValue指向的内存位置。整个函数的返回值就是交换是否成功的BOOL值。
1 2 3 4 5 6 |
while (YES) { void *ptr = objectPtr; if (OSAtomicCompareAndSwapPtrBarrier(ptr, NULL, &objectPtr)) { break; } } |
在这个while的死循环里面只有当OSAtomicCompareAndSwapPtrBarrier返回值为YES,才能退出整个死循环。返回值为YES就代表&objectPtr被置为了NULL,这样就确保了在线程安全的情况下,不存在野指针的问题了。
2. setKeyPath: onObject:
1 2 3 |
- (RACDisposable *)setKeyPath:(NSString *)keyPath onObject:(NSObject *)object { return [self setKeyPath:keyPath onObject:object nilValue:nil]; } |
setKeyPath: onObject:就是调用setKeyPath: onObject: nilValue:方法,只不过nilValue传递的是nil。
在ReactiveCocoa 过程中,除去RACSignal和RACSubject这些信号类以外,有些时候我们可能还需要封装一些固定的操作集合。这些操作集合都是固定的,每次只要一触发就会执行事先定义好的一个过程。在iOS开发过程中,按钮的点击事件就可能有这种需求。那么RACCommand就可以实现这种需求。
当然除了封装一个操作集合以外,RACCommand还能集中处理错误等等功能。今天就来从底层来看看RACCommand是如何实现的。
首先说说RACCommand的作用。
RACCommand 在ReactiveCocoa 中是对一个动作的触发条件以及它产生的触发事件的封装。
RACCommand最常见的例子就是在注册登录的时候,点击获取验证码的按钮,这个按钮的点击事件和触发条件就可以用RACCommand来封装,触发条件是一个信号,它可以是验证手机号,验证邮箱,验证身份证等一些验证条件产生的enabledSignal。触发事件就是按钮点击之后执行的事件,可以是发送验证码的网络请求。
RACCommand在ReactiveCocoa中算是很特别的一种存在,因为它的实现并不是FRP实现的,是OOP实现的。RACCommand的本质就是一个对象,在这个对象里面封装了4个信号。
关于RACCommand的定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
@interface RACCommand : NSObject @property (nonatomic, strong, readonly) RACSignal *executionSignals; @property (nonatomic, strong, readonly) RACSignal *executing; @property (nonatomic, strong, readonly) RACSignal *enabled; @property (nonatomic, strong, readonly) RACSignal *errors; @property (atomic, assign) BOOL allowsConcurrentExecution; volatile uint32_t _allowsConcurrentExecution;
@property (atomic, copy, readonly) NSArray *activeExecutionSignals; NSMutableArray *_activeExecutionSignals;
@property (nonatomic, strong, readonly) RACSignal *immediateEnabled; @property (nonatomic, copy, readonly) RACSignal * (^signalBlock)(id input); @end |
RACCommand中4个最重要的信号就是定义开头的那4个信号,executionSignals,executing,enabled,errors。需要注意的是,这4个信号基本都是(并不是完全是)在主线程上执行的。
1. RACSignal *executionSignals
executionSignals是一个高阶信号,所以在使用的时候需要进行降阶操作,降价操作在前面分析过了,在ReactiveCocoa v2.5中只支持3种降阶方式,flatten,switchToLatest,concat。降阶的方式就根据需求来选取。
还有选择原则是,如果在不允许Concurrent并发的RACCommand中一般使用switchToLatest。如果在允许Concurrent并发的RACCommand中一般使用flatten。
2. RACSignal *executing
executing这个信号就表示了当前RACCommand是否在执行,信号里面的值都是BOOL类型的。YES表示的是RACCommand正在执行过程中,命名也说明的是正在进行时ing。NO表示的是RACCommand没有被执行或者已经执行结束。
3. RACSignal *enabled
enabled信号就是一个开关,RACCommand是否可用。这个信号除去以下2种情况会返回NO:
除去以上2种情况以外,enabled信号基本都是返回YES。
4. RACSignal *errors
errors信号就是RACCommand执行过程中产生的错误信号。这里特别需要注意的是:在对RACCommand进行错误处理的时候,我们不应该使用subscribeError:对RACCommand的executionSignals
进行错误的订阅,因为executionSignals这个信号是不会发送error事件的,那当RACCommand包裹的信号发送error事件时,我们要怎样去订阅到它呢?应该用subscribeNext:去订阅错误信号。
1 2 3 |
[commandSignal.errors subscribeNext:^(NSError *x) { NSLog(@"ERROR! --> %@",x); }]; |
5. BOOL allowsConcurrentExecution
allowsConcurrentExecution是一个BOOL变量,它是用来表示当前RACCommand是否允许并发执行。默认值是NO。
如果allowsConcurrentExecution为NO,那么RACCommand在执行过程中,enabled信号就一定都返回NO,不允许并发执行。如果allowsConcurrentExecution为YES,允许并发执行。
如果是允许并发执行的话,就会出现多个信号就会出现一起发送值的情况。那么这种情况产生的高阶信号一般可以采取flatten(等效于flatten:0,+merge:)的方式进行降阶。
这个变量在具体实现中是用的volatile原子的操作,在实现中重写了它的get和set方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// 重写 get方法 - (BOOL)allowsConcurrentExecution { return _allowsConcurrentExecution != 0; }
// 重写 set方法 - (void)setAllowsConcurrentExecution:(BOOL)allowed { [self willChangeValueForKey:@keypath(self.allowsConcurrentExecution)];
if (allowed) { OSAtomicOr32Barrier(1, &_allowsConcurrentExecution); } else { OSAtomicAnd32Barrier(0, &_allowsConcurrentExecution); }
[self didChangeValueForKey:@keypath(self.allowsConcurrentExecution)]; } |
OSAtomicOr32Barrier是原子运算,它的意义是进行逻辑的“或”运算。通过原子性操作访问被volatile修饰的_allowsConcurrentExecution对象即可保障函数只执行一次。相应的OSAtomicAnd32Barrier也是原子运算,它的意义是进行逻辑的“与”运算。
6. NSArray *activeExecutionSignals
这个NSArray数组里面装了一个个有序排列的,执行中的信号。NSArray的数组是可以被KVO监听的。
1 2 3 4 5 |
- (NSArray *)activeExecutionSignals { @synchronized (self) { return [_activeExecutionSignals copy]; } } |
当然内部还有一个NSMutableArray的版本,NSArray数组是它的copy版本,使用它的时候需要加上线程锁,进行线程安全的保护。
在RACCommand内部,是对NSMutableArray数组进行操作的,在这里可变数组里面进行增加和删除的操作。
1 2 3 4 5 6 7 8 9 10 |
- (void)addActiveExecutionSignal:(RACSignal *)signal { NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
@synchronized (self) { NSIndexSet *indexes = [NSIndexSet indexSetWithIndex:_activeExecutionSignals.count]; [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; [_activeExecutionSignals addObject:signal]; [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; } } |
在往数组里面添加数据的时候是满足KVO的,这里对index进行了NSKeyValueChangeInsertion监听。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (void)removeActiveExecutionSignal:(RACSignal *)signal { NSCParameterAssert([signal isKindOfClass:RACSignal.class]);
@synchronized (self) { NSIndexSet *indexes = [_activeExecutionSignals indexesOfObjectsPassingTest:^ BOOL (RACSignal *obj, NSUInteger index, BOOL *stop) { return obj == signal; }];
if (indexes.count == 0) return;
[self willChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; [_activeExecutionSignals removeObjectsAtIndexes:indexes]; [self didChange:NSKeyValueChangeRemoval valuesAtIndexes:indexes forKey:@keypath(self.activeExecutionSignals)]; } } |
在移除数组里面也是依照indexes来进行移除的。注意,增加和删除的操作都必须包在@synchronized (self)中保证线程安全。
1 2 3 |
+ (BOOL)automaticallyNotifiesObserversForKey:(NSString *)key { return NO; } |
从上面增加和删除的操作中我们可以看见了RAC的作者在手动发送change notification,手动调用willChange: 和 didChange:方法。作者的目的在于防止一些不必要的swizzling可能会影响到增加和删除的操作,所以这里选择的手动发送通知的方式。
美团博客上这篇ReactiveCocoa核心元素与信号流文章里面对activeExecutionSignals的变化引起的一些变化画了一张数据流图:
除去没有影响到enabled信号,activeExecutionSignals的变化会影响到其他三个信号。
7. RACSignal *immediateEnabled
这个信号也是一个enabled信号,但是和之前的enabled信号不同的是,它并不能保证在main thread主线程上,它可以在任意一个线程上。
8. RACSignal * (^signalBlock)(id input)
这个闭包返回值是一个信号,这个闭包是在初始化RACCommand的时候会用到,下面分析源码的时候会出现。
1 2 3 |
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock; - (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock; - (RACSignal *)execute:(id)input; |
RACCommand 暴露出来的就3个方法,2个初始化方法和1个execute:的方法,接下来就来分析一下这些方法的底层实现。
首先先来看看比较短的那个初始化方法。
1 2 3 |
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock { return [self initWithEnabled:nil signalBlock:signalBlock]; } |
initWithSignalBlock:方法实际就是调用了initWithEnabled: signalBlock:方法。
1 2 3 |
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock {
} |
initWithSignalBlock:方法相当于第一个参数传的是nil的initWithEnabled: signalBlock:方法。第一个参数是enabledSignal,第二个参数是signalBlock的闭包。enabledSignal如果传的是nil,那么就相当于是传进了[RACSignal return:@YES]。
接下来详细分析一下initWithEnabled: signalBlock:方法的实现。
这个方法的实现非常长,需要分段来分析。RACCommand的初始化就是对自己的4个信号,executionSignals,executing,enabled,errors的初始化。
1. executionSignals信号的初始化
1 2 3 4 5 6 7 8 9 10 11 |
RACSignal *newActiveExecutionSignals = [[[[[self rac_valuesAndChangesForKeyPath:@keypath(self.activeExecutionSignals) options:NSKeyValueObservingOptionNew observer:nil]
reduceEach:^(id _, NSDictionary *change) { NSArray *signals = change[NSKeyValueChangeNewKey]; if (signals == nil) return [RACSignal empty];
return [signals.rac_sequence signalWithScheduler:RACScheduler.immediateScheduler]; }] concat] publish] autoconnect]; |
通过rac_valuesAndChangesForKeyPath: options: observer: 方法监听self.activeExecutionSignals数组里面是否有增加新的信号。rac_valuesAndChangesForKeyPath: options: observer: 方法的返回时是一个RACTuple,它的定义是这样的:RACTuplePack(value, change)。
只要每次数组里面加入了新的信号,那么rac_valuesAndChangesForKeyPath: options: observer: 方法就会把新加的值和change字典包装成RACTuple返回。再对这个信号进行一次reduceEach:操作。
举个例子,change字典可能是如下的样子:
1 2 3 4 5 6 7 |
{ indexes = "[number of indexes: 1 (in 1 ranges), indexes: (0)]"; kind = 2; new = ( " name: " ); } |
取出change[NSKeyValueChangeNewKey]就能取出每次变化新增的信号数组,然后把这个数组通过signalWithScheduler:转换成信号。
把原信号中每个值是里面装满RACTuple的信号通过变换,变换成了装满RACSingnal的三阶信号,通过concat进行降阶操作,降阶成了二阶信号。最后通过publish和autoconnect操作,把冷信号转换成热信号。
newActiveExecutionSignals最终是一个二阶热信号。
接下来再看看executionSignals是如何变换而来的。
1 2 3 4 5 6 |
_executionSignals = [[[newActiveExecutionSignals map:^(RACSignal *signal) { return [signal catchTo:[RACSignal empty]]; }] deliverOn:RACScheduler.mainThreadScheduler] setNameWithFormat:@"%@ -executionSignals", self]; |
executionSignals把newActiveExecutionSignals中错误信号都换成空信号。经过map变换之后,executionSignals是newActiveExecutionSignals的无错误信号的版本。由于map只是变换并没有降阶,所以executionSignals还是一个二阶的高阶冷信号。
注意最后加上了deliverOn,executionSignals信号每个值都是在主线程中发送的。
2. errors信号的初始化
在RACCommand中会搜集其所有的error信号,都装进自己的errors的信号中。这也是RACCommand的特点之一,能把错误统一处理。
1 2 3 4 5 6 7 8 9 |
RACMulticastConnection *errorsConnection = [[[newActiveExecutionSignals flattenMap:^(RACSignal *signal) { return [[signal ignoreValues] catch:^(NSError *error) { return [RACSignal return:error]; }]; }] deliverOn:RACScheduler.mainThreadScheduler] publish]; |
从上面分析中,我们知道,newActiveExecutionSignals最终是一个二阶热信号。这里在errorsConnection的变换中,我们对这个二阶的热信号进行flattenMap:降阶操作,只留下所有的错误信号,最后把所有的错误信号都装在一个低阶的信号中,这个信号中每个值都是一个error。同样,变换中也追加了deliverOn:操作,回到主线程中去操作。最后把这个冷信号转换成热信号,但是注意,还没有connect。
1 2 |
_errors = [errorsConnection.signal setNameWithFormat:@"%@ -errors", self]; [errorsConnection connect]; |
假设某个订阅者在RACCommand中的信号已经开始执行之后才订阅的,如果错误信号是一个冷信号,那么订阅之前的错误就接收不到了。所以错误应该是一个热信号,不管什么时候订阅都可以接收到所有的错误。
error信号就是热信号errorsConnection传出来的一个热信号。error信号每个值都是在主线程上发送的。
3. executing信号的初始化
executing这个信号表示了当前RACCommand是否在执行,信号里面的值都是BOOL类型的。那么如何拿到这样一个BOOL信号呢?
1 2 3 |
RACSignal *immediateExecuting = [RACObserve(self, activeExecutionSignals) map:^(NSArray *activeSignals) { return @(activeSignals.count > 0); }]; |
由于self.activeExecutionSignals是可以被KVO的,所以每当activeExecutionSignals变化的时候,判断当前数组里面是否还有信号,如果数组里面有值,就代表了当前有在执行中的信号。
1 2 3 4 5 6 |
_executing = [[[[[immediateExecuting deliverOn:RACScheduler.mainThreadScheduler] startWith:@NO] distinctUntilChanged] replayLast] setNameWithFormat:@"%@ -executing", self]; |
immediateExecuting信号表示当前是否有信号在执行。初始值为NO,一旦immediateExecuting不为NO的时候就会发出信号。最后通过replayLast转换成永远只保存最新的一个值的热信号。
executing信号除去第一个默认值NO,其他的每个值也是在主线程中发送的。
4. enabled信号的初始化
1 2 3 4 |
RACSignal *moreExecutionsAllowed = [RACSignal if:RACObserve(self, allowsConcurrentExecution) then:[RACSignal return:@YES] else:[immediateExecuting not]]; |
先监听self.allowsConcurrentExecution变量是否有变化,allowsConcurrentExecution默认值为NO。如果有变化,allowsConcurrentExecution为YES,就说明允许并发执行,那么就返回YES的RACSignal,allowsConcurrentExecution为NO,就说明不允许并发执行,那么就要看当前是否有正在执行的信号。immediateExecuting就是代表当前是否有在执行的信号,对这个信号取非,就是是否允许执行下一个信号的BOOL值。这就是moreExecutionsAllowed的信号。
1 2 3 4 5 6 7 8 |
if (enabledSignal == nil) { enabledSignal = [RACSignal return:@YES]; } else { enabledSignal = [[[enabledSignal startWith:@YES] takeUntil:self.rac_willDeallocSignal] replayLast]; } |
这里的代码就说明了,如果第一个参数传的是nil,那么就相当于传进来了一个[RACSignal return:@YES]信号。
如果enabledSignal不为nil,就在enabledSignal信号前面插入一个YES的信号,目的是为了防止传入的enabledSignal虽然不为nil,但是里面是没有信号的,比如[RACSignal never],[RACSignal empty],这些信号传进来也相当于是没用的,所以在开头加一个YES的初始值信号。
最后同样通过replayLast操作转换成只保存最新的一个值的热信号。
1 2 3 |
_immediateEnabled = [[RACSignal combineLatest:@[ enabledSignal, moreExecutionsAllowed ]] and]; |
这里涉及到了combineLatest:的变换操作,这个操作在之前的文章里面分析过了,这里不再详细分析源码实现。combineLatest:的作用就是把后面数组里面传入的每个信号,不管是谁发送出来一个信号,都会把数组里面所有信号的最新的值组合到一个RACTuple里面。immediateEnabled会把每个RACTuple里面的元素都进行逻辑and运算,这样immediateEnabled信号里面装的也都是BOOL值了。
immediateEnabled信号的意义就是每时每刻监听RACCommand是否可以enabled。它是由2个信号进行and操作得来的。每当allowsConcurrentExecution变化的时候就会产生一个信号,此时再加上enabledSignal信号,就能判断这一刻RACCommand是否能够enabled。每当enabledSignal变化的时候也会产生一个信号,再加上allowsConcurrentExecution是否允许并发,也能判断这一刻RACCommand是否能够enabled。所以immediateEnabled是由这两个信号combineLatest:之后再进行and操作得来的。
1 2 3 4 5 6 |
_enabled = [[[[[self.immediateEnabled take:1] concat:[[self.immediateEnabled skip:1] deliverOn:RACScheduler.mainThreadScheduler]] distinctUntilChanged] replayLast] setNameWithFormat:@"%@ -enabled", self]; |
由上面源码可以知道,self.immediateEnabled是由enabledSignal, moreExecutionsAllowed组合而成的。根据源码,enabledSignal的第一个信号值一定是[RACSignal return:@YES],moreExecutionsAllowed是RACObserve(self, allowsConcurrentExecution)产生的,由于allowsConcurrentExecution默认值是NO,所以moreExecutionsAllowed的第一个值是[immediateExecuting not]。
这里比较奇怪的地方是为何要用一次concat操作,把第一个信号值和后面的连接起来。如果直接写[self.immediateEnabled deliverOn:RACScheduler.mainThreadScheduler],那么整个self.immediateEnabled就都在主线程上了。作者既然没有这么写,肯定是有原因的。
This signal will send its current value upon subscription, and then all future values on the main thread.
通过查看文档,明白了作者的意图,作者的目的是为了让第一个值以后的每个值都发送在主线程上,所以这里skip:1之后接着deliverOn:RACScheduler.mainThreadScheduler。那第一个值呢?第一个值在一订阅的时候就发送出去了,同订阅者所在线程一致。
distinctUntilChanged保证enabled信号每次状态变化的时候只取到一个状态值。最后调用replayLast转换成只保存最新值的热信号。
从源码上看,enabled信号除去第一个值以外的每个值也都是在主线程上发送的。
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 *)execute:(id)input { // 1 BOOL enabled = [[self.immediateEnabled first] boolValue]; if (!enabled) { NSError *error = [NSError errorWithDomain:RACCommandErrorDomain code:RACCommandErrorNotEnabled userInfo:@{ NSLocalizedDescriptionKey: NSLocalizedString(@"The command is disabled and cannot be executed", nil),RACUnderlyingCommandErrorKey: self }];
return [RACSignal error:error]; } // 2 RACSignal *signal = self.signalBlock(input); NSCAssert(signal != nil, @"nil signal returned from signal block for value: %@", input); // 3 RACMulticastConnection *connection = [[signal subscribeOn:RACScheduler.mainThreadScheduler] multicast:[RACReplaySubject subject]];
@weakify(self); // 4 [self addActiveExecutionSignal:connection.signal]; [connection.signal subscribeError:^(NSError *error) { @strongify(self); // 5 [self removeActiveExecutionSignal:connection.signal]; } completed:^{ @strongify(self); // 5 [self removeActiveExecutionSignal:connection.signal]; }];
[connection connect]; // 6 return [connection.signal setNameWithFormat:@"%@ -execute: %@", self, [input rac_description]]; } |
把上述代码分成6步来分析:
RACCommand在日常iOS开发过程中,很适合上下拉刷新,按钮点击等操作,所以ReactiveCocoa就帮我们在这些UI控件上封装了一个RACCommand属性——rac_command。
1. UIBarButtonItem+RACCommandSupport
一旦UIBarButtonItem被点击,RACCommand就会执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
- (RACCommand *)rac_command { return objc_getAssociatedObject(self, UIControlRACCommandKey); }
- (void)setRac_command:(RACCommand *)command { objc_setAssociatedObject(self, UIControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 检查已经存储过的信号,移除老的,添加一个新的 RACDisposable *disposable = objc_getAssociatedObject(self, UIControlEnabledDisposableKey); [disposable dispose];
if (command == nil) return;
disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; objc_setAssociatedObject(self, UIControlEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self rac_hijackActionAndTargetIfNeeded]; } |
给UIBarButtonItem添加rac_command属性用到了runtime里面的AssociatedObject关联对象。这里给UIBarButtonItem类新增了2个关联对象,key分别是UIControlRACCommandKey,UIControlEnabledDisposableKey。UIControlRACCommandKey对应的是绑定的command,UIControlEnabledDisposableKey对应的是command.enabled的disposable信号。
set方法里面最后会调用rac_hijackActionAndTargetIfNeeded,这个方法需要特别注意:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
- (void)rac_hijackActionAndTargetIfNeeded { SEL hijackSelector = @selector(rac_commandPerformAction:); if (self.target == self && self.action == hijackSelector) return;
if (self.target != nil) NSLog(@"WARNING: UIBarButtonItem.rac_command hijacks the control's existing target and action.");
self.target = self; self.action = hijackSelector; }
- (void)rac_commandPerformAction:(id)sender { [self.rac_command execute:sender]; } |
rac_hijackActionAndTargetIfNeeded方法是对当前UIBarButtonItem的target和action进行检查。
如果当前UIBarButtonItem的target = self,并且action = @selector(rac_commandPerformAction:),那么就算检查通过符合执行RACCommand的前提条件了,直接return。
如果上述条件不符合,就强制改变UIBarButtonItem的target = self,并且action = @selector(rac_commandPerformAction:),所以这里需要注意的就是,UIBarButtonItem调用rac_command,会被强制改变它的target和action。
2. UIButton+RACCommandSupport
一旦UIButton被点击,RACCommand就会执行。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
- (RACCommand *)rac_command { return objc_getAssociatedObject(self, UIButtonRACCommandKey); }
- (void)setRac_command:(RACCommand *)command { objc_setAssociatedObject(self, UIButtonRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
RACDisposable *disposable = objc_getAssociatedObject(self, UIButtonEnabledDisposableKey); [disposable dispose];
if (command == nil) return;
disposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self]; objc_setAssociatedObject(self, UIButtonEnabledDisposableKey, disposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[self rac_hijackActionAndTargetIfNeeded]; } |
这里给UIButton添加绑定2个属性同样也用到了runtime里面的AssociatedObject关联对象。代码和UIBarButtonItem的实现基本一样。同样是给UIButton类新增了2个关联对象,key分别是UIButtonRACCommandKey,UIButtonEnabledDisposableKey。UIButtonRACCommandKey对应的是绑定的command,UIButtonEnabledDisposableKey对应的是command.enabled的disposable信号。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
- (void)rac_hijackActionAndTargetIfNeeded { SEL hijackSelector = @selector(rac_commandPerformAction:);
for (NSString *selector in [self actionsForTarget:self forControlEvent:UIControlEventTouchUpInside]) { if (hijackSelector == NSSelectorFromString(selector)) { return; } }
[self addTarget:self action:hijackSelector forControlEvents:UIControlEventTouchUpInside]; }
- (void)rac_commandPerformAction:(id)sender { [self.rac_command execute:sender]; } |
rac_hijackActionAndTargetIfNeeded函数的意思和之前的一样,也是检查UIButton的target和action。最终结果的UIButton的target = self,action = @selector(rac_commandPerformAction:)
3. UIRefreshControl+RACCommandSupport
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 |
- (RACCommand *)rac_command { return objc_getAssociatedObject(self, UIRefreshControlRACCommandKey); }
- (void)setRac_command:(RACCommand *)command { objc_setAssociatedObject(self, UIRefreshControlRACCommandKey, command, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[objc_getAssociatedObject(self, UIRefreshControlDisposableKey) dispose];
if (command == nil) return;
RACDisposable *enabledDisposable = [command.enabled setKeyPath:@keypath(self.enabled) onObject:self];
RACDisposable *executionDisposable = [[[[self rac_signalForControlEvents:UIControlEventValueChanged] map:^(UIRefreshControl *x) { return [[[command execute:x] catchTo:[RACSignal empty]] then:^{ return [RACSignal return:x]; }]; }] concat] subscribeNext:^(UIRefreshControl *x) { [x endRefreshing]; }];
RACDisposable *commandDisposable = [RACCompoundDisposable compoundDisposableWithDisposables:@[ enabledDisposable, executionDisposable ]]; objc_setAssociatedObject(self, UIRefreshControlDisposableKey, commandDisposable, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } |
这里给UIRefreshControl添加绑定2个属性同样也用到了runtime里面的AssociatedObject关联对象。代码和UIBarButtonItem的实现基本一样。同样是给UIButton类新增了2个关联对象,key分别是UIRefreshControlRACCommandKey,UIRefreshControlDisposableKey。UIRefreshControlRACCommandKey对应的是绑定的command,UIRefreshControlDisposableKey对应的是command.enabled的disposable信号。
这里多了一个executionDisposable信号,这个信号是用来结束刷新操作的。
1 |
[[[command execute:x] catchTo:[RACSignal empty]] then:^{ return [RACSignal return:x]; }]; |
这个信号变换先把RACCommand执行,执行之后得到的结果信号剔除掉所有的错误。then操作就是忽略掉所有值,在最后添加一个返回UIRefreshControl对象的信号。
[self rac_signalForControlEvents:UIControlEventValueChanged]之后再map升阶为高阶信号,所以最后用concat降阶。最后订阅这个信号,订阅只会收到一个值,command执行完毕之后的信号发送完所有的值的时候,即收到这个值的时刻就是最终刷新结束的时刻。
所以最终的disposable信号还要加上executionDisposable。