為了弄清楚"map與flattenMap有什么區(qū)別"這個(gè)問題,對(duì)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]);
}];
這里多考慮一步,可以對(duì)一個(gè)信號(hào)進(jìn)行訂閱讽营,為什么對(duì)信號(hào)的map返回結(jié)果也可以訂閱?
下面將map處理過(guò)程涉及到的源碼貼出來(lái),
// <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的流轉(zhuǎn)看起來(lái)有些復(fù)雜宦芦,用一張圖來(lái)簡(jiǎn)化:
每個(gè)block的具體說(shuō)明:
1> block是map的參數(shù),這個(gè)block里邊就是對(duì)信號(hào)的值做轉(zhuǎn)換轴脐;
2> block_map是map方法提供給flattenMap的參數(shù)(我們使用block后加map來(lái)標(biāo)志block經(jīng)過(guò)了map)调卑;
3> block_map_flattenMap是flattenMap方法提供給bind的參數(shù)(我們使用block_map后加flattenMap標(biāo)志block_map經(jīng)過(guò)了flattenMap)抡砂;
圖中黃色標(biāo)記分別記錄了每個(gè)對(duì)應(yīng)block的內(nèi)容。其中涉及到的block名字根據(jù)上邊描述做了替換恬涧∽⒁妫可以很方便地看出map->flattenMap->bind這個(gè)流程對(duì)最初的轉(zhuǎn)換信號(hào)值block做了層層包裹。
下面看一下關(guān)鍵的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) { // 對(duì)應(yīng)于說(shuō)明1
// Manually check disposal to handle synchronous errors.
if (compoundDisposable.disposed) return;
BOOL stop = NO;
id signal = bindingBlock(x, &stop); // 對(duì)應(yīng)于說(shuō)明2
@autoreleasepool {
if (signal != nil) addSignal(signal); // 對(duì)應(yīng)于說(shuō)明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.
《訂閱原始信號(hào)溯捆,也就是self丑搔,也就是示例代碼中接收map消息的signal》
* 2. Any time the original signal sends a value, transform it using the binding block.
《當(dāng)原始信號(hào)發(fā)出值時(shí),使用binding block進(jìn)行轉(zhuǎn)換提揍,這個(gè)binding block對(duì)應(yīng)上面源代碼中bindingBlock啤月,對(duì)應(yīng)上圖中block_map_flattenMap那個(gè)block里的return值,
根據(jù)上圖對(duì)block_map_flattenMap層層解套劳跃,最終是調(diào)用了轉(zhuǎn)換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這個(gè)block對(duì)返回的signal進(jìn)行訂閱强重。》
* 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這三點(diǎn)對(duì)照上面代碼,可以用語(yǔ)言描述一下<map示例代碼1>:對(duì)signal發(fā)送map消息艺智,返回一個(gè)signal_rt倘要,這個(gè)signal_rt就是bind方法的返回值;對(duì)signal_rt進(jìn)行訂閱十拣,然后就進(jìn)入了bind注解的1封拧,2,3流程夭问。
flattenMap與map有什么區(qū)別
有了上面的知識(shí)基礎(chǔ)泽西,再來(lái)看flattenMap與map有什么區(qū)別這個(gè)問題。
flattenMap和map的主要區(qū)別在于block_map_flattenMap中的block_map()缰趋,map提供的block_map是這樣的:
^(id value) {
return [class return:block(value)];
}
經(jīng)過(guò)查看[class return:block(value)]的內(nèi)部調(diào)用捧杉,其實(shí)[class return:block(value)]可以用 [RACReturnSignal return:block(value)]來(lái)代替。所以map提供的block_map(value)其實(shí)就是一個(gè)RACReturnSignal秘血,map轉(zhuǎn)換后的值被保存在了RACReturnSignal的value屬性中味抖。
而flattenMap提供的block_map()是什么呢?在使用flattenMap時(shí)block_map()是我們需要提供的block參數(shù)灰粮,我們可以返回任意類型的信號(hào)仔涩,不僅僅是RACReturnSignal。
下面看一個(gè)涉及到map與flattenMap使用區(qū)別的一個(gè)例子: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并不會(huì)出現(xiàn)登錄結(jié)果粘舟,參考上面的結(jié)論看一下問題出在了哪熔脂,
1> 使用map時(shí)block_map(value)是RACReturnSignal佩研,其對(duì)應(yīng)的value是- (RACSignal *)signInSignal返回的信號(hào),根據(jù)bind源碼說(shuō)明第3條锤悄,會(huì)對(duì)RACReturnSignal進(jìn)行訂閱韧骗,根據(jù)RACReturnSignal使用方法訂閱者最終得到的是- (RACSignal *)signInSignal返回的信號(hào)嘉抒;
2> 使用flattenMap時(shí)零聚,block_map(value)就是- (RACSignal *)signInSignal的返回登錄信號(hào),然后根據(jù)bind源碼說(shuō)明第3條些侍,會(huì)對(duì)這個(gè)登錄信號(hào)進(jìn)行訂閱隶症。
結(jié)論
所以flattenMap和map的區(qū)別在于,flattenMap的block參數(shù)返回一個(gè)“任意類型”信號(hào)RACSignal到bind內(nèi)部去做addSignal(RACSignal)操作來(lái)對(duì)RACSignal進(jìn)行訂閱岗宣;
而map是限定flattenMap只能返回一個(gè)RACReturnSignal信號(hào)去bind內(nèi)部做addSigna(RACReturenSignal)操作來(lái)對(duì)RACReturnSignal進(jìn)行訂閱蚂会,而對(duì)RACReturnSignal進(jìn)行訂閱只能獲取RACReturnSignal內(nèi)部攜帶的value值。