为了弄清楚"map与flattenMap有什么区别"这个问题,对flattenMap背后的bind方法做一些深入了解。
bind源代码理解
先看一段使用map的示例代码:
// <map示例代码1>
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@(99)];
[subscriber sendCompleted];
return nil;
}];
[[signal map:^id(id value) {
return @([value integerValue] + 1);
}] subscribeNext:^(id x) {
NSLog(@"%d", [x integerValue]);
}];
这里多考虑一步,可以对一个信号进行订阅,为什么对信号的map返回结果也可以订阅?
下面将map处理过程涉及到的源码贴出来,
// <map源代码>
- (instancetype)map:(id (^)(id value))block {
NSCParameterAssert(block != nil);
Class class = self.class;
RACStream *stream = [[self flattenMap:^(id value) {
return [class return:block(value)];
}] setNameWithFormat:@"[%@] -map:", self.name];
return stream;
}
// <flattenMap源代码>
- (instancetype)flattenMap:(RACStream * (^)(id value))block {
Class class = self.class;
RACStream *stream = [[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];
return stream;
}
block的流转看起来有些复杂,用一张图来简化:
每个block的具体说明:
1> block是map的参数,这个block里边就是对信号的值做转换;
2> block_map是map方法提供给flattenMap的参数(我们使用block后加map来标志block经过了map);
3> block_map_flattenMap是flattenMap方法提供给bind的参数(我们使用block_map后加flattenMap标志block_map经过了flattenMap);
图中黄色标记分别记录了每个对应block的内容。其中涉及到的block名字根据上边描述做了替换。可以很方便地看出map->flattenMap->bind这个流程对最初的转换信号值block做了层层包裹。
下面看一下关键的bind代码:
// <bind源代码>
- (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
NSCParameterAssert(block != NULL);
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
* 2. Any time the original signal sends a value, transform it using the binding block.
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
RACSignal *signal = [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
RACStreamBindBlock bindingBlock = block();
NSMutableArray *signals = [NSMutableArray arrayWithObject:self];
RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
void (^completeSignal)(RACSignal *, RACDisposable *) = ^(RACSignal *signal, RACDisposable *finishedDisposable) {
BOOL removeDisposable = NO;
@synchronized (signals) {
[signals removeObject:signal];
if (signals.count == 0) {
[subscriber sendCompleted];
[compoundDisposable dispose];
} else {
removeDisposable = YES;
}
}
if (removeDisposable) [compoundDisposable removeDisposable:finishedDisposable];
};
void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
@synchronized (signals) {
[signals addObject:signal];
}
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
[subscriber sendNext:x];
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(signal, selfDisposable);
}
}];
selfDisposable.disposable = disposable;
};
@autoreleasepool {
RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
[compoundDisposable addDisposable:selfDisposable];
RACDisposable *bindingDisposable = [self subscribeNext:^(id x) { // 对应于说明1
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop); // 对应于说明2
@autoreleasepool {
if (signal != nil) addSignal(signal); // 对应于说明3
if (signal == nil || stop) {
[selfDisposable dispose];
completeSignal(self, selfDisposable);
}
}
} error:^(NSError *error) {
[compoundDisposable dispose];
[subscriber sendError:error];
} completed:^{
@autoreleasepool {
completeSignal(self, selfDisposable);
}
}];
selfDisposable.disposable = bindingDisposable;
}
return compoundDisposable;
}] setNameWithFormat:@"[%@] -bind:", self.name];
return signal;
}
代码头部给的这一段注解讲得很清楚:
/*
* -bind: should:
*
* 1. Subscribe to the original signal of values.
《订阅原始信号,也就是self,也就是示例代码中接收map消息的signal》
* 2. Any time the original signal sends a value, transform it using the binding block.
《当原始信号发出值时,使用binding block进行转换,这个binding block对应上面源代码中bindingBlock,对应上图中block_map_flattenMap那个block里的return值,
根据上图对block_map_flattenMap层层解套,最终是调用了转换value值的block。》
* 3. If the binding block returns a signal, subscribe to it, and pass all of its values through to the subscriber as they're received.
《如果bindingBlock返回的是signal,使用addSignal这个block对返回的signal进行订阅。》
* 4. If the binding block asks the bind to terminate, complete the _original_ signal.
* 5. When _all_ signals complete, send completed to the subscriber.
*
* If any signal sends an error at any point, send that to the subscriber.
*/
1,2,3这三点对照上面代码,可以用语言描述一下<map示例代码1>:对signal发送map消息,返回一个signal_rt,这个signal_rt就是bind方法的返回值;对signal_rt进行订阅,然后就进入了bind注解的1,2,3流程。
flattenMap与map有什么区别
有了上面的知识基础,再来看flattenMap与map有什么区别这个问题。
flattenMap和map的主要区别在于block_map_flattenMap中的block_map(),map提供的block_map是这样的:
^(id value) {
return [class return:block(value)];
}
经过查看[class return:block(value)]的内部调用,其实[class return:block(value)]可以用 [RACReturnSignal return:block(value)]来代替。所以map提供的block_map(value)其实就是一个RACReturnSignal,map转换后的值被保存在了RACReturnSignal的value属性中。
而flattenMap提供的block_map()是什么呢?在使用flattenMap时block_map()是我们需要提供的block参数,我们可以返回任意类型的信号,不仅仅是RACReturnSignal。
下面看一个涉及到map与flattenMap使用区别的一个例子:ReactiveCocoa入门教程:第一部分
- (RACSignal *)signInSignal {
return [RACSignal createSignal:^RACDisposable *(id subscriber){
[self.signInService
signInWithUsername:self.usernameTextField.text
password:self.passwordTextField.text
complete:^(BOOL success){
[subscriber sendNext:@(success)];
[subscriber sendCompleted];
}];
return nil;
}];
}
[[[self.signInButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
map:^id(id x){
return [self signInSignal];
}]
subscribeNext:^(id x){
NSLog(@"Sign in result: %@", x);
}];
上面使用map并不会出现登录结果,参考上面的结论看一下问题出在了哪,
1> 使用map时block_map(value)是RACReturnSignal,其对应的value是- (RACSignal *)signInSignal返回的信号,根据bind源码说明第3条,会对RACReturnSignal进行订阅,根据RACReturnSignal使用方法订阅者最终得到的是- (RACSignal *)signInSignal返回的信号;
2> 使用flattenMap时,block_map(value)就是- (RACSignal *)signInSignal的返回登录信号,然后根据bind源码说明第3条,会对这个登录信号进行订阅。
结论
所以flattenMap和map的区别在于,flattenMap的block参数返回一个“任意类型”信号RACSignal到bind内部去做addSignal(RACSignal)操作来对RACSignal进行订阅;
而map是限定flattenMap只能返回一个RACReturnSignal信号去bind内部做addSigna(RACReturenSignal)操作来对RACReturnSignal进行订阅,而对RACReturnSignal进行订阅只能获取RACReturnSignal内部携带的value值。