bind:
在RAC中有着举足轻重的作用,没有它,很多功能都是没有办法实现,之前的例子中我们也了解到-eagerSequence
、-lazySequence
这两个方法的区别,以及flattenMap
、 skip
、 take
、 takeUntilBlock
、 skipUntilBlock
、 distinctUntilChanged
等function都用到了bind
方法。
我们可以发现很多地方都继承和重写bind方法。
首先看看源码中的解释说明。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点要求,以及最后一条的说明。
- 需要订阅原始signal。
- 不管原始signal何时发送数据,都会将value转换到bind的block中。
- 如果block返回一个signal,则订阅这个signal。
- block终止绑定则原始signal发送完成。
- 当所有的signal都完成时才会发送对subscriber发送completed。
- 当任何一个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];
}
这个函数体比较大,我们慢慢分析一下。
- creat了一个新的signal。所有的操作都是在这个创建的过程中。
- 是一个complete的block。 这里维护了一个锁(原子操作的互斥锁)。
- 是一个addSignal的block。在block内部会订阅参数传进来的signal。
- 在这个@autoreleasepool中,会订阅原始的signal。在订阅的过程中,如果bindingBlock返回了一个signal,进入(3)的addSignal的block。如果bindingBlock要stop绑定,则进入(2)complete的Block。
例子就不说了,我们经常使用的map
,flatMap
调用的就是bind
。
以上是RACSignal
的bind
操作。
RACSequence的bind
- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
RACStreamBindBlock bindBlock = block();
return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
}
将block
copy一份存储在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;
关于eagerSequence
和lazySequence
的关系,可以看之前的章节(RACSignal、RACSequence、RACTuple也有例子说明)。
由于最后一行被注释掉了,所以控制台不会输出任何内容。当使用了seq1.array
的时候,才会触发[seq1 map:]
。
当获取调用栈的时候,发现,在调用array方法之后,由于重写了NSFastEnumeration
协议中的方法,才会调用相关的方法
#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;
}
大致的分析一下:
- 由于我们在使用
seq1.array
时,调用了NSFastEnumeration
的方法;
- 其实现内部使用了
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];
}
我们具体的来分析一下:
- 首先将block存储起来
- 调用了array的方法,这里调用的array跟RACSequence调用的完全不一样。
- (NSArray *)array {
return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}
- 直接便利当前的数组,同时获取
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
中的内容。
当前的调用栈,如下图:
总结
以上时bind的相关内容,signal的bind只有一种。不用做区分。而sequence的bind最常用的有两种:一种是RACSequence
使用RACDynamicSequence
来存储value的懒加载模式;一种是RACEagerSequence
直接调用模式。
上述方法中用到的是map
函数,在map
内部调用的是flatMap
。flatMap
内部调用的bind
。
flatMap
与map
的区别:
- map的返回值是一个value。
- flatMap返回值是一个
RACStream
写的不好,欢迎各位大神指正。喜欢的点赞,加个关注,谢谢!