RACStream
在 ReactiveCocoa 框架中,RACStream 是所有信号流的抽象类,所有基于流的操作都可以建立在该类之上。
其包含5个需要子类重写的方法:empty、return:、bind:、concat:、zipwith:。
empty 方法返回一个空的流,而 return: 方法则返回包含一个指定值的流。
+ (__kindof RACStream<ValueType> *)empty; + (__kindof RACStream<ValueType> *)return:(nullable ValueType)value;
bind: 方法则是创建一个新的流来表示原始流与 block 的绑定,RACStreamBindBlock 类型是包含两个参数并且返回一个流的代码块。
typedef RACStream * _Nullable (^RACStreamBindBlock)(ValueType _Nullable value, BOOL *stop); - (__kindof RACStream *)bind:(RACStreamBindBlock (^)(void))block;
concat: 方法将两个流拼接到一起作为一个新的流,这拼接的两个流的类型应当是一致的。
- (__kindof RACStream *)concat:(RACStream *)stream;
zipWith: 方法将压缩两个同类型的流,产生一个新的流,新流的数据是两个流的相同序列号的数据压缩而来,直到有一个流结束,则新流结束。
- (__kindof RACStream *)zipWith:(RACStream *)stream;
扩展类
在 RACStream 的扩展类中定义了一个 name 属性,该属性用于调试,相应的设置该值的方法如下:
- (instancetype)setNameWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2);
但是只有设置了环境变量 RAC_DEBUG_SIGNAL_NAMES
该方法才有效,即 getenv("RAC_DEBUG_SIGNAL_NAMES") != NULL
该方法才会修改 name 属性的值。
分类
RACStream 有一个 Operations 分类,其中定义了一些常用的操作方法,这些方法不宜被重写,即使重写会有更好的性能。
flattenMap:
- (__kindof RACStream *)flattenMap: (__kindof RACStream * _Nullable (^)(ValueType _Nullable value))block;
映射方法,即将接收到的信号量通过参数 block 映射为需要的值。该 block 参数的 value 参数即为原始流传递的信号量,最后经过处理后,得到一个新的 RACStream 信号流,该信号流中包含有处理的信号量,但也可能是空流。
- (__kindof RACStream *)flattenMap:(__kindof RACStream * (^)(id value))block { Class class = self.class; return [[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]; }
通过上面的源码可知,映射方法,实则是调用 bind: 方法,创建了一个新的信号流,该信号流被订阅时,会将 block 代码块参数绑定到源信号流,那么每当源信号流产生了信号量时,就可以通过执行与其绑定的 block 代码块创建一个新的内部信号流,这个内部信号流中的信号量是经过处理的,而其将会转发给调用该方法创建的信号流的订阅者进行进一步处理。
- (__kindof RACStream *)flatten;
如果说 flattenMap: 方法是根据源信号流中的信号量经过处理产生新的信号流的话,那么 flatten 方法则是直接将源信号流中的信号量作为返回值,即该信号量本身就是信号流类型。那么,可以知道,源信号流中传递的信号量就是信号流。
- (__kindof RACStream *)flatten { return [[self flattenMap:^(id value) { return value; }] setNameWithFormat:@"[%@] -flatten", self.name]; }
所以,flatten 方法返回的新的信号流所接收的信号量是源信号流中传递的信号流中传递的信号量,有点类似于解压了一个信号流从而获得其中诸多信号流中的信号量。
- (__kindof RACStream *)map:(id _Nullable (^)(ValueType _Nullable value))block;
map: 方法则是将信号量进行处理,然后返回一个值,该值被 return: 方法用来初始化一个只包含一个信号量的信号流。
- (__kindof RACStream *)map:(id (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^(id value) { return [class return:block(value)]; }] setNameWithFormat:@"[%@] -map:", self.name]; }
- (__kindof RACStream *)mapReplace:(nullable id)object;
该方法实际是调用了 map: 方法,并且其 block 中直接返回了 object 值,即直接用指定的值替换接收到的信号量。
- (__kindof RACStream<ValueType> *)filter:(BOOL (^)(ValueType _Nullable value))block;
通过 block 来判断符合条件的信号量,如果符合,就将信号量封装到信号流中进行返回,否则,就返回空的流。
- (__kindof RACStream *)filter:(BOOL (^)(id value))block { NSCParameterAssert(block != nil); Class class = self.class; return [[self flattenMap:^ id (id value) { if (block(value)) { return [class return:value]; } else { return class.empty; } }] setNameWithFormat:@"[%@] -filter:", self.name]; }
- (__kindof RACStream<ValueType> *)ignore:(nullable ValueType)value;
忽略指定的值,该方法实际是调用了 filter: 方法,只是在 block 参数中将信号量同指定的值进行比较。
- (__kindof RACStream *)reduceEach:(RACReduceBlock)reduceBlock;
该方法将接收到的组合信号量进行处理,得到一个单一值的信号量。
_Pragma("clang diagnostic push") \ _Pragma("clang diagnostic ignored \"-Wstrict-prototypes\"") \ typedef id _Nonnull (^RACReduceBlock)(); _Pragma("clang diagnostic pop") - (__kindof RACStream *)reduceEach:(RACReduceBlock)reduceBlock { NSCParameterAssert(reduceBlock != nil); __weak RACStream *stream __attribute__((unused)) = self; return [[self map:^(RACTuple *t) { NSCAssert([t isKindOfClass:RACTuple.class], @"Value from stream %@ is not a tuple: %@", stream, t); return [RACBlockTrampoline invokeBlock:reduceBlock withArguments:t]; }] setNameWithFormat:@"[%@] -reduceEach:", self.name]; }
从上面的代码可知,源信号流所传递的信号量必须是 RACTuple 类型的,其所包含的分量个数与 reduceBlock 的参数个数需要保持一致,所以 RACReduceBlock 类型的参数个数是不确定的,当然框架中最多支持 15 个参数。
- (__kindof RACStream<ValueType> *)skip:(NSUInteger)skipCount;
跳过 skipCount 次信号量,被跳过的信号量实际返回了空的信号流。
- (__kindof RACStream *)skip:(NSUInteger)skipCount { Class class = self.class; return [[self bind:^{ __block NSUInteger skipped = 0; return ^(id value, BOOL *stop) { if (skipped >= skipCount) return [class return:value]; skipped++; return class.empty; }; }] setNameWithFormat:@"[%@] -skip: %lu", self.name, (unsigned long)skipCount]; }
- (__kindof RACStream<ValueType> *)take:(NSUInteger)count;
只接收第 count 次信号量,如果 count 为 0,那么接收第一个信号量时返回空的信号流。当接收到第 count 次传递的信号量后,则不再接收信号量。
- (__kindof RACStream<ValueType> *)takeUntilBlock:(BOOL (^)(ValueType _Nullable x))predicate;
一直接收信号量,直到信号量满足 predicate 的条件为止,如果,predicate 不可能返回 YES ,那么返回的信号流同源信号流是等同的。
- (__kindof RACStream *)takeUntilBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); Class class = self.class; return [[self bind:^{ return ^ id (id value, BOOL *stop) { if (predicate(value)) return nil; return [class return:value]; }; }] setNameWithFormat:@"[%@] -takeUntilBlock:", self.name]; }
- (__kindof RACStream<ValueType> *)takeWhileBlock:(BOOL (^)(ValueType _Nullable x))predicate;
一直接收信号量,直到信号量不满足 predicate 的条件为止,该方法实际是调用了 takeUntilBlock: 方法,只是判断条件取反而以。
- (__kindof RACStream<ValueType> *)skipUntilBlock:(BOOL (^)(ValueType _Nullable x))predicate;
跳过信号量,直到满足 predicate 的条件为止。
- (__kindof RACStream *)skipUntilBlock:(BOOL (^)(id x))predicate { NSCParameterAssert(predicate != nil); Class class = self.class; return [[self bind:^{ __block BOOL skipping = YES; return ^ id (id value, BOOL *stop) { if (skipping) { if (predicate(value)) { skipping = NO; } else { return class.empty; } } return [class return:value]; }; }] setNameWithFormat:@"[%@] -skipUntilBlock:", self.name]; }
- (__kindof RACStream<ValueType> *)skipWhileBlock:(BOOL (^)(ValueType _Nullable x))predicate;
跳过信号量,直到不满足 predicate 的条件为止,该方法实际是调用了 skipUtilBlock: 方法,只是其参数取反了而已。
- (__kindof RACStream<ValueType> *)distinctUntilChanged;
当接收到的信号量同上一个信号量比较,发生了改变时,才将信号量返回。
- (__kindof RACStream *)distinctUntilChanged { Class class = self.class; return [[self bind:^{ __block id lastValue = nil; __block BOOL initial = YES; return ^(id x, BOOL *stop) { if (!initial && (lastValue == x || [x isEqual:lastValue])) return [class empty]; initial = NO; lastValue = x; return [class return:x]; }; }] setNameWithFormat:@"[%@] -distinctUntilChanged", self.name]; }
- (__kindof RACStream<ValueType> *)startWith:(nullable ValueType)value;
将指定的值作为一个信号量封装到一个信号流中并将源信号流拼接在其后,生成一个新的信号流。
- (__kindof RACStream *)startWith:(id)value { return [[[self.class return:value] concat:self] setNameWithFormat:@"[%@] -startWith: %@", self.name, RACDescription(value)]; }
该方法会产生一个新的信号流,而后订阅该流的信号。
+ (__kindof RACStream *)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block;
通过 block 参数对一系列的信号流进行组合,组合的过程是将第一个信号流的信号量使用 RACTuplePack 进行封装,并且得到一个新的信号流,然后将这个新的信号流同第二个信号流通过 block 进行处理得到新的信号流,该信号流再与第三个信号流进行处理,以此类推,最终得到一个拥有层层封装的信号量的信号流,而后再对这个信号流的信号量进行解封操作。
+ (__kindof RACStream *)join:(id<NSFastEnumeration>)streams block:(RACStream * (^)(id, id))block { RACStream *current = nil; // Creates streams of successively larger tuples by combining the input // streams one-by-one. for (RACStream *stream in streams) { // For the first stream, just wrap its values in a RACTuple. That way, // if only one stream is given, the result is still a stream of tuples. if (current == nil) { current = [stream map:^(id x) { return RACTuplePack(x); }]; continue; } current = block(current, stream); } if (current == nil) return [self empty]; return [current map:^(RACTuple *xs) { // Right now, each value is contained in its own tuple, sorta like: // // (((1), 2), 3) // // We need to unwrap all the layers and create a tuple out of the result. NSMutableArray *values = [[NSMutableArray alloc] init]; while (xs != nil) { [values insertObject:xs.last ?: RACTupleNil.tupleNil atIndex:0]; xs = (xs.count > 1 ? xs.first : nil); } return [RACTuple tupleWithObjectsFromArray:values]; }]; }
+ (__kindof RACStream<ValueType> *)zip:(id<NSFastEnumeration>)streams;
压缩多个信号流的信号量,这个方法实际是调用的上面的方法,只是其 block 这种处理两个流的方法为 zipWith: 方法。
+ (__kindof RACStream *)zip:(id<NSFastEnumeration>)streams { return [[self join:streams block:^(RACStream *left, RACStream *right) { return [left zipWith:right]; }] setNameWithFormat:@"+zip: %@", streams]; }
zip:reduce:
+ (__kindof RACStream<ValueType> *)zip:(id<NSFastEnumeration>)streams reduce:(RACGenericReduceBlock)reduceBlock;
这个方法实际是 zip: 和 reduceEach: 方法的组合,先将流组合在一起,然后将封装的信号量进行解压处理。
+ (__kindof RACStream *)zip:(id<NSFastEnumeration>)streams reduce:(RACGenericReduceBlock)reduceBlock { NSCParameterAssert(reduceBlock != nil); RACStream *result = [self zip:streams]; // Although we assert this condition above, older versions of this method // supported this argument being nil. Avoid crashing Release builds of // apps that depended on that. if (reduceBlock != nil) result = [result reduceEach:reduceBlock]; return [result setNameWithFormat:@"+zip: %@ reduce:", streams]; }
scanWithStart:reduceWithIndex:
- (__kindof RACStream *)scanWithStart:(nullable id)startingValue reduceWithIndex:(id _Nullable (^)(id _Nullable running, ValueType _Nullable next, NSUInteger index))reduceBlock;
该方法的作用是将接收到的信号量通过 block 进行合并,起始值为 startingValue 值,而后将其与第一个信号量进行组合处理,得到的结果同第二个信号量进行组合处理,以此类推。
- (__kindof RACStream *)scanWithStart:(id)startingValue reduceWithIndex:(id (^)(id, id, NSUInteger))reduceBlock { NSCParameterAssert(reduceBlock != nil); Class class = self.class; return [[self bind:^{ __block id running = startingValue; __block NSUInteger index = 0; return ^(id value, BOOL *stop) { running = reduceBlock(running, value, index++); return [class return:running]; }; }] setNameWithFormat:@"[%@] -scanWithStart: %@ reduceWithIndex:", self.name, RACDescription(startingValue)]; }
从上面的代码可知,每一次对新接收的信号量进行合并后,合并结果都会传递给订阅者以供进一步处理。
scanWithStart:reduce:
- (__kindof RACStream *)scanWithStart:(nullable id)startingValue reduce:(id _Nullable (^)(id _Nullable running, ValueType _Nullable next))reduceBlock;
这个方法实际是调用了上面的方法,只是在 reduceBlock 参数中,不再提供接收到的信号量序列号(从 0 开始计数)。
combinePreviousWithStart:reduce:
- (__kindof RACStream *)combinePreviousWithStart:(nullable ValueType)start reduce:(id _Nullable (^)(ValueType _Nullable previous, ValueType _Nullable current))reduceBlock;
这个方法类似于上面的方法,但是其区别在于,接收的信号量只会同上一个信号量进行合并处理,而其结果不会参与下面的合并处理,同样的,将 start 值看作上一个信号量同实际接收到的第一个信号量进行处理,而后接收到的第二个信号量同第一个信号量进行处理。
- (__kindof RACStream *)combinePreviousWithStart:(id)start reduce:(id (^)(id previous, id next))reduceBlock { NSCParameterAssert(reduceBlock != NULL); return [[[self scanWithStart:RACTuplePack(start) reduce:^(RACTuple *previousTuple, id next) { id value = reduceBlock(previousTuple[0], next); return RACTuplePack(next, value); }] map:^(RACTuple *tuple) { return tuple[1]; }] setNameWithFormat:@"[%@] -combinePreviousWithStart: %@ reduce:", self.name, RACDescription(start)]; }
从代码可以看出,其是调用了 scanWithStart:reduce: 方法,只是在 reduceBlock 中返回的值是 next 值与 value 的封装,这样每一次调用 reduceBlock 时,都可以获得 next 值,而方法最后返回的信号流也可以获取到 value 值作为信号量。
通过上面方法的分析可知,所有的方法最终都会调用 empty、return:、bind:、concat: 或 zipwith: ,而这些方法则是需要子类重写的。