ReactiveCocoa 中 集合類 RACTuple/RACSequence
本篇目錄
1. 解釋一下`RACTuple`中的一些難點(diǎn);
2. RACSequence底層實(shí)現(xiàn)分析。
一. 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其實(shí)是同一個(gè)方法,實(shí)現(xiàn)很簡(jiǎn)單,其實(shí)就是初始化一個(gè)
RACTuple
類型的對(duì)象,至于為什么使用[[self alloc] init]
,而不使用[[RACTuple alloc] init]
是為了當(dāng)RACTuple
子類調(diào)用的時(shí)候可以直接初始化子類,根據(jù)convert
入?yún)⒌牟煌瑏?lái)判斷對(duì)數(shù)組的內(nèi)部是否的Null對(duì)象替換成RACTupleNil.tupleNil
,很簡(jiǎn)單不對(duì)說(shuō)了; - 我們看下方法3,
- va_list用于聲明一個(gè)變量,我們知道函數(shù)的可變參數(shù)列表其實(shí)就是一個(gè)字符串,所以va_list才被聲明為字符型指針,這個(gè)類型用于聲明一個(gè)指向參數(shù)列表的字符型指針變量,例如:va_list ap;//ap:arguement pointer
- va_start(ap,v),它的第一個(gè)參數(shù)是指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)函數(shù)的第一個(gè)參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個(gè)數(shù)。
- va_arg(ap,t),它的第一個(gè)參數(shù)指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)的類型。
- va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL)。
由上面4個(gè)引用應(yīng)該沒(méi)什么問(wèn)題了。
- 通過(guò)下標(biāo)取出元素
通常OC的集合類比如NSArray
、NSDictionary
等等都可以直接通過(guò)下標(biāo)來(lái)取出值,比如我們初始化一個(gè)NSArray *testArray = @[@"1",@"2",@"3"]; 然后我們直接testArray[0]就可以拿到@"1";我們發(fā)現(xiàn)RACTuple
類也可以直接通過(guò)下標(biāo)獲取
```
- (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];
}
```
這是怎么做到的呢,關(guān)鍵在于下面的代碼
```
@implementation RACTuple (ObjectSubscripting)
- (id)objectAtIndexedSubscript:(NSUInteger)idx {
return [self objectAtIndex:idx];
}
@end
```
原因就是`RACTuple`類在分類中實(shí)現(xiàn)了`objectAtIndexedSubscript`這個(gè)方法。
- 神奇的
RACTupleUnpack
和RACTuplePack
宏
代碼追蹤可以知道RACTuplePack
這個(gè)宏其實(shí)就是
```
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
```
這個(gè)倒沒(méi)什么好說(shuō)的,上面已經(jīng)解釋過(guò)了,就是便利初始化方法
RACTupleUnpack
通過(guò)一個(gè)demo來(lái)說(shuō)明這個(gè)宏到底是干什么的
- (void)testTupleDesign {
RACTupleUnpack(NSString *string, NSNumber *num) = RACTuplePack(@"foo",@(10));
}
通過(guò)下面的方式查看編譯時(shí)的代碼
也就是下面這個(gè)
__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, ]]);
去掉一些干擾項(xiàng)其實(shí)就是
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);
相當(dāng)于這個(gè)
dic[@"key"] = value
對(duì)應(yīng)上面
- dic --->
[RACTupleUnpackingTrampoline trampoline]
- key --->
[NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ]
- value --->
([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]])
這就很奇怪了不是一般只有NSDictionary
對(duì)象才會(huì)有這種附值得嗎,為什么RACTupleUnpackingTrampoline
也可以呢
首先來(lái)看一下下面的demo,
- (void)testTupleDesign:(NSMutableDictionary *)dic {
dic[@"key"] = @"2";
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testTupleDesign:@[]];
}
我們都知道如果這樣寫(xiě)的話,當(dāng)點(diǎn)擊view的時(shí)候就會(huì)產(chǎn)生crash,控制臺(tái)會(huì)輸出
-[__NSArray0 setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x61000001b150
相信這種錯(cuò)誤有一定開(kāi)發(fā)經(jīng)驗(yàn)的人并不陌生
setObject:forKeyedSubscript:
我們?cè)賮?lái)看看RACTupleUnpackingTrampoline
的實(shí)現(xiàn)
@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];
}];
}
好吧它重寫(xiě)了這個(gè)方法
4 實(shí)現(xiàn)了NSFastEnumeration
協(xié)議
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
}
關(guān)于這個(gè)協(xié)議的上篇已經(jīng)有了詳細(xì)的解釋
二. RACSequence底層實(shí)現(xiàn)分析。
(1) 先看看頭文件
@interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration>
@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個(gè)協(xié)議;既然是一個(gè)集合必定會(huì)有元素,
head
表示的是第一個(gè)元素;tail
表示的是尾部,不過(guò)返回值還是RACSequence
有點(diǎn)鏈?zhǔn)降囊馑迹咸茁妨?,通過(guò)一直返回當(dāng)前對(duì)象達(dá)到鏈?zhǔn)秸{(diào)用的目的;-
array
返回RACSequence
對(duì)象里面裝著的所有對(duì)象;- (NSArray *)array { NSMutableArray *array = [NSMutableArray array]; for (id obj in self) { [array addObject:obj]; } return [array copy];
}
```
之所以能夠for...in...是因?yàn)閷?shí)現(xiàn)了`NSFastEnumeration`協(xié)議,詳細(xì)請(qǐng)看[這篇](http://www.lxweimin.com/p/7a2ffb5e171c)
-
objectEnumerator
通過(guò)她可以遍歷所有的對(duì)象- (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
這個(gè)后面單獨(dú)講。
(2) 再看初始化方法
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}
是不是想起了RACSignal
的初始化?有點(diǎn)差不多的意思實(shí)際初始化的是RACSequence
的子類RACDynamicSequence
,實(shí)際是保存了2個(gè)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;
}
細(xì)心的同學(xué)肯定發(fā)現(xiàn)了還有一個(gè)方法
+ (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;
}
這個(gè)方法是有在bind
的時(shí)候才會(huì)用到,這兩個(gè)方法的不同之處就是多了個(gè)dependencyBlock
,我們看看RACSequence
對(duì)bind
是如何實(shí)現(xiàn)了
- (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;
//如果當(dāng)前對(duì)象的head為空表示沒(méi)有數(shù)據(jù)了返回
if (value == nil) {
stop = YES;
return nil;
}
//執(zhí)行bindBlock
current = (id)bindBlock(value, &stop);
if (current == nil) {
stop = YES;
return nil;
}
//取出下一個(gè)對(duì)象賦值給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,并沒(méi)有調(diào)用block,那么哪里會(huì)調(diào)用到這個(gè)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的時(shí)候,就會(huì)調(diào)用headBlock( ),并且把返回值作為入?yún)ⅲ?/p>
- (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;
}
}
在調(diào)用tailBlock
前也會(huì)調(diào)用dependencyBlock。
我們?cè)倩氐?code>bind方法
headBlock是入?yún)閕d,直接返回passthroughSequence的head,并不使用入?yún)ⅰ?/p>
tailBlock是入?yún)閕d,返回值為RACSequence。由于RACSequence的定義類似遞歸定義的,所以tailBlock會(huì)再次遞歸調(diào)用bind:passingThroughValuesFromSequence:產(chǎn)生一個(gè)RACSequence作為新的sequence的tail供下次調(diào)用。
dependencyBlock就是成為了headBlock和tailBlock閉包執(zhí)行之前要執(zhí)行的閉包。
dependencyBlock的目的是為了把原來(lái)的sequence里面的值,都進(jìn)行一次變換。current是入?yún)assthroughSequence,valuesSeq就是原sequence的引用。每次循環(huán)一次就取出原sequence的頭,直到取不到為止,就是遍歷完成。
取出valuesSeq的head,傳入bindBlock( )閉包進(jìn)行變換,返回值是一個(gè)current 的sequence。在每次headBlock和tailBlock之前都會(huì)調(diào)用這個(gè)dependencyBlock,變換后新的sequence的head就是current的head,新的sequence的tail就是遞歸調(diào)用傳入的current.tail。
上面是當(dāng)需要使用block的時(shí)候再去調(diào)用,RACSequence
還有另一種bind
的實(shí)現(xiàn)方式,那就是她的子類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];
}
可以看出這個(gè)地方直接就調(diào)用了bindBlock
。
通過(guò)一個(gè)例子說(shuō)明兩種bind
的區(qū)別
- (void)testSequece {
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
}
這個(gè)時(shí)候控制臺(tái)是沒(méi)有打印的,因?yàn)橹皇潜4媪薭lock;
若想有打印
- (void)testSequece {
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
[lazySequence array];
}
因?yàn)?code>[lazySequence array]方法的內(nèi)部會(huì)調(diào)用.head,因?yàn)閷?shí)現(xiàn)了NSFastEnumeration
,for循環(huán)實(shí)際是自定義的循環(huán)會(huì)走到- (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);
}];
}
這樣會(huì)變成RACEagerSequence
對(duì)象,從而達(dá)到及時(shí)調(diào)用的目的。
可以通過(guò)signal
方法將RACSquence
和RACSignal
關(guān)聯(lián)上