ReactiveCocoa的bind源码理解

为了弄清楚"map与flattenMap有什么区别"这个问题,对flattenMap背后的bind方法做一些深入了解。

bind源代码理解

先看一段使用map的示例代码:

// 
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id 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处理过程涉及到的源码贴出来,

// 
- (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;
}

// 
- (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的流转看起来有些复杂,用一张图来简化:


ReactiveCocoa的bind源码理解_第1张图片
map方法block流转过程.png

每个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代码:

// 
- (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 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这三点对照上面代码,可以用语言描述一下:对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值。

你可能感兴趣的:(ReactiveCocoa的bind源码理解)