RAC-bind那些事

作者: 不会飞的小白 | 来源:发表于2017-04-05 15:27 被阅读331次

    bind:在RAC中有着举足轻重的作用,没有它,很多功能都是没有办法实现,之前的例子中我们也了解到-eagerSequence-lazySequence 这两个方法的区别,以及flattenMapskiptaketakeUntilBlockskipUntilBlockdistinctUntilChanged等function都用到了bind方法。

    我们可以发现很多地方都继承和重写bind方法。


    bind.png

    首先看看源码中的解释说明。RACStream.h

    /// Lazily binds a block to the values in the receiver.
    ///
    /// This should only be used if you need to terminate the bind early, or close
    /// over some state. -flattenMap: is more appropriate for all other cases.
    ///
    /// block - A block returning a RACStreamBindBlock. This block will be invoked
    ///         each time the bound stream is re-evaluated. This block must not be
    ///         nil or return nil.
    ///
    /// Returns a new stream which represents the combined result of all lazy
    /// applications of `block`.
    - (instancetype)bind:(RACStreamBindBlock (^)(void))block;
    

    RACSignal的bind

    在RACSignal.m中的实现,有如下说明:

    /*
     * -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.
     */
    

    也就是说了在bind:的操作中需要遵循那5点要求,以及最后一条的说明。

    1. 需要订阅原始signal。
    2. 不管原始signal何时发送数据,都会将value转换到bind的block中。
    3. 如果block返回一个signal,则订阅这个signal。
    4. block终止绑定则原始signal发送完成。
    5. 当所有的signal都完成时才会发送对subscriber发送completed。
    6. 当任何一个signal发送error时,则直接发送给订阅者。
    - (RACSignal *)bind:(RACStreamBindBlock (^)(void))block {
        NSCParameterAssert(block != NULL);
    
        return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
            RACStreamBindBlock bindingBlock = block();
    
            __block volatile int32_t signalCount = 1;   // indicates self
    
            RACCompoundDisposable *compoundDisposable = [RACCompoundDisposable compoundDisposable];
    
            void (^completeSignal)(RACDisposable *) = ^(RACDisposable *finishedDisposable) {
                if (OSAtomicDecrement32Barrier(&signalCount) == 0) {
                    [subscriber sendCompleted];
                    [compoundDisposable dispose];
                } else {
                    [compoundDisposable removeDisposable:finishedDisposable];
                }
            };
    
            void (^addSignal)(RACSignal *) = ^(RACSignal *signal) {
                OSAtomicIncrement32Barrier(&signalCount);
    
                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(selfDisposable);
                    }
                }];
    
                selfDisposable.disposable = disposable;
            };
    
            @autoreleasepool {
                RACSerialDisposable *selfDisposable = [[RACSerialDisposable alloc] init];
                [compoundDisposable addDisposable:selfDisposable];
    
                RACDisposable *bindingDisposable = [self subscribeNext:^(id x) {
                    // Manually check disposal to handle synchronous errors.
                    if (compoundDisposable.disposed) return;
    
                    BOOL stop = NO;
                    id signal = bindingBlock(x, &stop);
    
                    @autoreleasepool {
                        if (signal != nil) addSignal(signal);
                        if (signal == nil || stop) {
                            [selfDisposable dispose];
                            completeSignal(selfDisposable);
                        }
                    }
                } error:^(NSError *error) {
                    [compoundDisposable dispose];
                    [subscriber sendError:error];
                } completed:^{
                    @autoreleasepool {
                        completeSignal(selfDisposable);
                    }
                }];
    
                selfDisposable.disposable = bindingDisposable;
            }
    
            return compoundDisposable;
        }] setNameWithFormat:@"[%@] -bind:", self.name];
    }
    

    这个函数体比较大,我们慢慢分析一下。

    1. creat了一个新的signal。所有的操作都是在这个创建的过程中。
    2. 是一个complete的block。 这里维护了一个锁(原子操作的互斥锁)。
    3. 是一个addSignal的block。在block内部会订阅参数传进来的signal。
    4. 在这个@autoreleasepool中,会订阅原始的signal。在订阅的过程中,如果bindingBlock返回了一个signal,进入(3)的addSignal的block。如果bindingBlock要stop绑定,则进入(2)complete的Block。

    例子就不说了,我们经常使用的mapflatMap调用的就是bind

    以上是RACSignalbind操作。

    RACSequence的bind

    - (instancetype)bind:(RACStreamBindBlock (^)(void))block {
        RACStreamBindBlock bindBlock = block();
        return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
    }
    

    blockcopy一份存储在bindBlock中,然后调用另一个方法。

    在这个方法中,我们又看到了一段注释。

    // Store values calculated in the dependency here instead, avoiding any kind
    // of temporary collection and boxing.
    //
    // This relies on the implementation of RACDynamicSequence synchronizing
    // access to its head, tail, and dependency, and we're only doing it because
    // we really need the performance.
    

    就是为了告诉我们将数据存储起来避免任何形式的临时处理,在以后的依赖关系处理中调用。 依赖RACDynamicSequence来同步处理相关head tail。这样做的目的是为了提高性能。

    - (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
        
        __block RACSequence *valuesSeq = self;
        __block RACSequence *current = passthroughSequence;
        __block BOOL stop = NO;
    
        RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
            while (current.head == nil) {
                if (stop) return nil;
    
                // We've exhausted the current sequence, create a sequence from the
                // next value.
                id value = valuesSeq.head;
    
                if (value == nil) {
                    // We've exhausted all the sequences.
                    stop = YES;
                    return nil;
                }
    
                current = (id)bindBlock(value, &stop);
                if (current == nil) {
                    stop = YES;
                    return nil;
                }
    
                valuesSeq = valuesSeq.tail;
            }
    
            NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current);
            return nil;
        } headBlock:^(id _) {
            return current.head;
        } tailBlock:^ id (id _) {
            if (stop) return nil;
    
            return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail];
        }];
    
        sequence.name = self.name;
        return sequence;
    }
    

    这里重点用到了一个RACDynamicSequence的一个方法。

    // RACDynamicSequence.m
    
    + (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock {
        NSCParameterAssert(dependencyBlock != nil);
        NSCParameterAssert(headBlock != nil);
    
        RACDynamicSequence *seq = [[RACDynamicSequence alloc] init];
        seq.headBlock = [headBlock copy];
        seq.tailBlock = [tailBlock copy];
        seq.dependencyBlock = [dependencyBlock copy];
        seq.hasDependency = YES;
        return seq;
    }
    

    这个方法中,主要是返回一个RACDynamicSequence类型的数据,并将各种block存储起来。

    接着上面的bind来说。在+sequenceWithLazyDependency的函数内部,只是将数据copy了一份存储起来,并没有做任何处理,在函数的调用时,并没有使用到block,所以数据是不会做处理的。只能在接下来的某些函数上会依赖这些存储的数据,才会用到这里的相关内容。比较绕,举个例子。

    NSArray *array1 = @[@"a", @"b", @"c"];
    
    RACSequence *seq1 = [array1.rac_sequence map:^id(id value) {
        NSLog(@"lazy value = %@", value);
        return value;
    }];
        
    // NSArray *array3 = seq1.array;
    

    关于eagerSequencelazySequence的关系,可以看之前的章节(RACSignal、RACSequence、RACTuple也有例子说明)。

    由于最后一行被注释掉了,所以控制台不会输出任何内容。当使用了seq1.array的时候,才会触发[seq1 map:]

    当获取调用栈的时候,发现,在调用array方法之后,由于重写了NSFastEnumeration协议中的方法,才会调用相关的方法

    lazy-array的调用栈.png
    #pragma mark NSFastEnumeration
    
    - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
        if (state->state == ULONG_MAX) {
            // Enumeration has completed.
            return 0;
        }
    
        // We need to traverse the sequence itself on repeated calls to this
        // method, so use the 'state' field to track the current head.
        RACSequence *(^getSequence)(void) = ^{
            return (__bridge RACSequence *)(void *)state->state;
        };
    
        void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) {
            // Release the old sequence and retain the new one.
            CFBridgingRelease((void *)state->state);
    
            state->state = (unsigned long)CFBridgingRetain(sequence);
        };
    
        void (^complete)(void) = ^{
            // Release any stored sequence.
            setSequence(nil);
            state->state = ULONG_MAX;
        };
    
        if (state->state == 0) {
            // Since a sequence doesn't mutate, this just needs to be set to
            // something non-NULL.
            state->mutationsPtr = state->extra;
    
            setSequence(self);
        }
    
        state->itemsPtr = stackbuf;
    
        NSUInteger enumeratedCount = 0;
        while (enumeratedCount < len) {
            RACSequence *seq = getSequence();
    
            // Because the objects in a sequence may be generated lazily, we want to
            // prevent them from being released until the enumerator's used them.
            __autoreleasing id obj = seq.head;
            if (obj == nil) {
                complete();
                break;
            }
    
            stackbuf[enumeratedCount++] = obj;
    
            if (seq.tail == nil) {
                complete();
                break;
            }
    
            setSequence(seq.tail);
        }
    
        return enumeratedCount;
    }
    

    大致的分析一下:

    1. 由于我们在使用seq1.array时,调用了NSFastEnumeration的方法;
    2. 其实现内部使用了seq.head
    #pragma mark RACSequence
    
    - (id)head {
        @synchronized (self) {
            id untypedHeadBlock = self.headBlock;
            if (untypedHeadBlock == nil) return _head;
    
            if (self.hasDependency) {
                if (self.dependencyBlock != nil) {
                    _dependency = self.dependencyBlock();
                    self.dependencyBlock = nil;
                }
    
                id (^headBlock)(id) = untypedHeadBlock;
                _head = headBlock(_dependency);
            } else {
                id (^headBlock)(void) = untypedHeadBlock;
                _head = headBlock();
            }
    
            self.headBlock = nil;
            return _head;
        }
    }
    

    3.在head的方法中调用了self.dependencyBlock()。这个block是之前RACDynamicSequence存储的一个block。

    4.这时会使用map^(){}中的的内容。

    以上是lazy的调用过程分析。接下来看看eager的map分析

    RACEagerSequence的bind

    因为在eagerSequence的方法中,返回的是一个RACEagerSequence类型的数据。

    // RACSequence.m
    
    - (RACSequence *)eagerSequence {
        return [RACEagerSequence sequenceWithArray:self.array offset:0];
    }
    
    // RACArraySequence.m(RACEagerSequence继承RACArraySequence)
    
    + (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
        NSCParameterAssert(offset <= array.count);
    
        if (offset == array.count) return self.empty;
    
        RACArraySequence *seq = [[self alloc] init];
        seq->_backingArray = [array copy];
        seq->_offset = offset;
        return seq;
    }
    

    这里存储了backingArray。下面的bind操作会用到。

    接下来,我们直接看看bind操作

    - (instancetype)bind:(RACStreamBindBlock (^)(void))block {
        NSCParameterAssert(block != nil);
        RACStreamBindBlock bindBlock = block();
        NSArray *currentArray = self.array;
        NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count];
        
        for (id value in currentArray) {
            BOOL stop = NO;
            RACSequence *boundValue = (id)bindBlock(value, &stop);
            if (boundValue == nil) break;
    
            for (id x in boundValue) {
                [resultArray addObject:x];
            }
    
            if (stop) break;
        }
        
        return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name];
    }
    

    我们具体的来分析一下:

    1. 首先将block存储起来
    2. 调用了array的方法,这里调用的array跟RACSequence调用的完全不一样。
    - (NSArray *)array {
          return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
    }
    
    1. 直接便利当前的数组,同时获取bindBlock()

    <b>举个例子:</b>

    NSArray *array2 = @[@1, @2, @3];
    
    RACSequence *seq2 = [array2.rac_sequence.eagerSequence map:^id(id value) {
        NSLog(@"eager value = %@", value);
        return value;
    }];
    

    这里即使不使用seq2.array也会直接输出NSLog中的内容。
    当前的调用栈,如下图:

    eager-array的调用栈.png

    总结

    以上时bind的相关内容,signal的bind只有一种。不用做区分。而sequence的bind最常用的有两种:一种是RACSequence使用RACDynamicSequence来存储value的懒加载模式;一种是RACEagerSequence直接调用模式。

    上述方法中用到的是map函数,在map内部调用的是flatMapflatMap内部调用的bind

    flatMapmap的区别:

    1. map的返回值是一个value。
    2. flatMap返回值是一个RACStream

    写的不好,欢迎各位大神指正。喜欢的点赞,加个关注,谢谢!

    相关文章

      网友评论

        本文标题:RAC-bind那些事

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