ReactiveCocoa的bind源码理解

作者: tom555cat | 来源:发表于2017-08-31 17:13 被阅读72次

    为了弄清楚"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的流转看起来有些复杂,用一张图来简化:


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

    // <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值。

    相关文章

      网友评论

        本文标题:ReactiveCocoa的bind源码理解

        本文链接:https://www.haomeiwen.com/subject/dltfjxtx.html