ReactiveCocoa 中 集合类 RACTuple/RACSequence
本篇目录
1. 解释一下`RACTuple`中的一些难点;
2. RACSequence底层实现分析。
一. RACTuple
- 先看初始化方法
//1.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}
// 2.
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils: (BOOL)convert {
RACTuple *tuple = [[self alloc] init];
if (convert) {
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
for (id object in array) {
[newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];
}
tuple.backingArray = newArray;
} else {
tuple.backingArray = [array copy];
}
return tuple;
}
//3.
+ (instancetype)tupleWithObjects:(id)object, ... {
RACTuple *tuple = [[self alloc] init];
va_list args;
va_start(args, object);
NSUInteger count = 0;
for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
++count;
}
va_end(args);
if (count == 0) {
tuple.backingArray = @[];
return tuple;
}
NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
va_start(args, object);
for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
[objects addObject:currentObject];
}
va_end(args);
tuple.backingArray = objects;
return tuple;
}
- 1和2其实是同一个方法,实现很简单,其实就是初始化一个
RACTuple
类型的对象,至于为什么使用[[self alloc] init]
,而不使用[[RACTuple alloc] init]
是为了当RACTuple
子类调用的时候可以直接初始化子类,根据convert
入参的不同,来判断对数组的内部是否的Null对象替换成RACTupleNil.tupleNil
,很简单不对说了; - 我们看下方法3,
- va_list用于声明一个变量,我们知道函数的可变参数列表其实就是一个字符串,所以va_list才被声明为字符型指针,这个类型用于声明一个指向参数列表的字符型指针变量,例如:va_list ap;//ap:arguement pointer
- va_start(ap,v),它的第一个参数是指向可变参数字符串的变量,第二个参数是可变参数函数的第一个参数,通常用于指定可变参数列表中参数的个数。
- va_arg(ap,t),它的第一个参数指向可变参数字符串的变量,第二个参数是可变参数的类型。
- va_end(ap) 用于将存放可变参数字符串的变量清空(赋值为NULL)。
由上面4个引用应该没什么问题了。
- 通过下标取出元素
通常OC的集合类比如NSArray
、NSDictionary
等等都可以直接通过下标来取出值,比如我们初始化一个NSArray *testArray = @[@"1",@"2",@"3"]; 然后我们直接testArray[0]就可以拿到@"1";我们发现RACTuple
类也可以直接通过下标获取
```
- (id)first {
return self[0];
}
- (id)second {
return self[1];
}
- (id)third {
return self[2];
}
- (id)fourth {
return self[3];
}
- (id)fifth {
return self[4];
}
- (id)last {
return self[self.count - 1];
}
```
这是怎么做到的呢,关键在于下面的代码
```
@implementation RACTuple (ObjectSubscripting)
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
return [self objectAtIndex:idx];
}
@end
```
原因就是`RACTuple`类在分类中实现了`objectAtIndexedSubscript`这个方法。
- 神奇的
RACTupleUnpack
和RACTuplePack
宏
代码追踪可以知道RACTuplePack
这个宏其实就是
```
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
```
这个倒没什么好说的,上面已经解释过了,就是便利初始化方法
RACTupleUnpack
通过一个demo来说明这个宏到底是干什么的
- (void)testTupleDesign {
RACTupleUnpack(NSString *string, NSNumber *num) = RACTuplePack(@"foo",@(10));
}
通过下面的方式查看编译时的代码
也就是下面这个
__attribute__((objc_ownership(strong))) id RACTupleUnpack875_var0;
__attribute__((objc_ownership(strong))) id RACTupleUnpack875_var1;
int RACTupleUnpack_state875 = 0;
RACTupleUnpack_after875: ;
__attribute__((objc_ownership(strong))) NSString *string = RACTupleUnpack875_var0;
__attribute__((objc_ownership(strong))) NSNumber *num = RACTupleUnpack875_var1;
if (RACTupleUnpack_state875 != 0) RACTupleUnpack_state875 = 2;
while (RACTupleUnpack_state875 != 2)
if (RACTupleUnpack_state875 == 1) { goto RACTupleUnpack_after875; }
else for (; RACTupleUnpack_state875 != 1; RACTupleUnpack_state875 = 1)
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);
去掉一些干扰项其实就是
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);
相当于这个
dic[@"key"] = value
对应上面
- dic --->
[RACTupleUnpackingTrampoline trampoline]
- key --->
[NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ]
- value --->
([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]])
这就很奇怪了不是一般只有NSDictionary
对象才会有这种附值得吗,为什么RACTupleUnpackingTrampoline
也可以呢
首先来看一下下面的demo,
- (void)testTupleDesign:(NSMutableDictionary *)dic {
dic[@"key"] = @"2";
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self testTupleDesign:@[]];
}
我们都知道如果这样写的话,当点击view的时候就会产生crash,控制台会输出
-[__NSArray0 setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x61000001b150
相信这种错误有一定开发经验的人并不陌生
setObject:forKeyedSubscript:
我们再来看看RACTupleUnpackingTrampoline
的实现
@interface RACTupleUnpackingTrampoline : NSObject
+ (instancetype)trampoline;
- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables;
@end
- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables {
NSCParameterAssert(variables != nil);
[variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) {
__strong id *ptr = (__strong id *)value.pointerValue;
*ptr = tuple[index];
}];
}
好吧它重写了这个方法
4 实现了NSFastEnumeration
协议
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
}
关于这个协议的上篇已经有了详细的解释
二. RACSequence底层实现分析。
(1) 先看看头文件
@interface RACSequence : RACStream
@property (nonatomic, strong, readonly) id head;
@property (nonatomic, strong, readonly) RACSequence *tail;
@property (nonatomic, copy, readonly) NSArray *array;
@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;
@property (nonatomic, copy, readonly) RACSequence *eagerSequence;
@property (nonatomic, copy, readonly) RACSequence *lazySequence;
RACSequence
是继承RACStream
的,分别遵循了3个协议;既然是一个集合必定会有元素,
head
表示的是第一个元素;tail
表示的是尾部,不过返回值还是RACSequence
有点链式的意思,老套路了,通过一直返回当前对象达到链式调用的目的;-
array
返回RACSequence
对象里面装着的所有对象;- (NSArray *)array { NSMutableArray *array = [NSMutableArray array]; for (id obj in self) { [array addObject:obj]; } return [array copy];
}
```
之所以能够for...in...是因为实现了`NSFastEnumeration`协议,详细请看[这篇](http://www.jianshu.com/p/7a2ffb5e171c)
-
objectEnumerator
通过她可以遍历所有的对象- (NSEnumerator *)objectEnumerator { RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init]; enumerator.sequence = self; return enumerator; } @interface RACSequenceEnumerator : NSEnumerator @property (nonatomic, strong) RACSequence *sequence; @end @implementation RACSequenceEnumerator - (id)nextObject { id object = nil; @synchronized (self) { object = self.sequence.head; self.sequence = self.sequence.tail; } return object; }
-
eagerSequence
和lazySequence
这个后面单独讲。
(2) 再看初始化方法
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}
是不是想起了RACSignal
的初始化?有点差不多的意思实际初始化的是RACSequence
的子类RACDynamicSequence
,实际是保存了2个block
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
NSCParameterAssert(headBlock != nil);
RACDynamicSequence *seq = [[RACDynamicSequence alloc] init];
seq.headBlock = [headBlock copy];
seq.tailBlock = [tailBlock copy];
seq.hasDependency = NO;
return seq;
}
细心的同学肯定发现了还有一个方法
+ (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;
}
这个方法是有在bind
的时候才会用到,这两个方法的不同之处就是多了个dependencyBlock
,我们看看RACSequence
对bind
是如何实现了
- (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;
id value = valuesSeq.head;
//如果当前对象的head为空表示没有数据了返回
if (value == nil) {
stop = YES;
return nil;
}
//执行bindBlock
current = (id)bindBlock(value, &stop);
if (current == nil) {
stop = YES;
return nil;
}
//取出下一个对象赋值给valuesSeq
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;
}
这里的bind
只是保存了block,并没有调用block,那么哪里会调用到这个block呢?
我们查看RACDynamicSequence
的head
和tail
方法可知
- (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;
}
}
要取出sequence的head的时候,就会调用headBlock( ),并且把返回值作为入参;
- (RACSequence *)tail {
@synchronized (self) {
id untypedTailBlock = self.tailBlock;
if (untypedTailBlock == nil) return _tail;
if (self.hasDependency) {
if (self.dependencyBlock != nil) {
_dependency = self.dependencyBlock();
self.dependencyBlock = nil;
}
RACSequence * (^tailBlock)(id) = untypedTailBlock;
_tail = tailBlock(_dependency);
} else {
RACSequence * (^tailBlock)(void) = untypedTailBlock;
_tail = tailBlock();
}
if (_tail.name == nil) _tail.name = self.name;
self.tailBlock = nil;
return _tail;
}
}
在调用tailBlock
前也会调用dependencyBlock。
我们再回到bind
方法
headBlock是入参为id,直接返回passthroughSequence的head,并不使用入参。
tailBlock是入参为id,返回值为RACSequence。由于RACSequence的定义类似递归定义的,所以tailBlock会再次递归调用bind:passingThroughValuesFromSequence:产生一个RACSequence作为新的sequence的tail供下次调用。
dependencyBlock就是成为了headBlock和tailBlock闭包执行之前要执行的闭包。
dependencyBlock的目的是为了把原来的sequence里面的值,都进行一次变换。current是入参passthroughSequence,valuesSeq就是原sequence的引用。每次循环一次就取出原sequence的头,直到取不到为止,就是遍历完成。
取出valuesSeq的head,传入bindBlock( )闭包进行变换,返回值是一个current 的sequence。在每次headBlock和tailBlock之前都会调用这个dependencyBlock,变换后新的sequence的head就是current的head,新的sequence的tail就是递归调用传入的current.tail。
上面是当需要使用block的时候再去调用,RACSequence
还有另一种bind
的实现方式,那就是她的子类RACEagerSequence
- (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];
}
可以看出这个地方直接就调用了bindBlock
。
通过一个例子说明两种bind
的区别
- (void)testSequece {
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
}
这个时候控制台是没有打印的,因为只是保存了block;
若想有打印
- (void)testSequece {
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
[lazySequence array];
}
因为[lazySequence array]
方法的内部会调用.head,因为实现了NSFastEnumeration
,for循环实际是自定义的循环会走到- (NSUInteger)countByEnumeratingWithState: objects:count:
方法
也可以这样
- (void)testSequece {
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence.eagerSequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
}
这样会变成RACEagerSequence
对象,从而达到及时调用的目的。
可以通过signal
方法将RACSquence
和RACSignal
关联上