×

RAC-bind那些事

96
不会飞的小白
2017.04.05 15:27* 字数 1229

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

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

ReactiveCocoa
Web note ad 1