ReactiveCocoa 中 集合類 RACTuple/RACSequence
本篇目錄
1. 解釋一下`RACTuple`中的一些難點俭正;
2. RACSequence底層實現(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其實是同一個方法,實現(xiàn)很簡單罐农,其實就是初始化一個
RACTuple
類型的對象,至于為什么使用[[self alloc] init]
,而不使用[[RACTuple alloc] init]
是為了當RACTuple
子類調(diào)用的時候可以直接初始化子類贝乎,根據(jù)convert
入?yún)⒌牟煌瑏砼袛鄬?shù)組的內(nèi)部是否的Null對象替換成RACTupleNil.tupleNil
叽粹,很簡單不對說了览效; - 我們看下方法3,
- va_list用于聲明一個變量虫几,我們知道函數(shù)的可變參數(shù)列表其實就是一個字符串锤灿,所以va_list才被聲明為字符型指針,這個類型用于聲明一個指向參數(shù)列表的字符型指針變量辆脸,例如:va_list ap;//ap:arguement pointer
- va_start(ap,v),它的第一個參數(shù)是指向可變參數(shù)字符串的變量但校,第二個參數(shù)是可變參數(shù)函數(shù)的第一個參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個數(shù)啡氢。
- va_arg(ap,t),它的第一個參數(shù)指向可變參數(shù)字符串的變量状囱,第二個參數(shù)是可變參數(shù)的類型术裸。
- va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL)。
由上面4個引用應該沒什么問題了亭枷。
- 通過下標取出元素
通常OC的集合類比如NSArray
袭艺、NSDictionary
等等都可以直接通過下標來取出值,比如我們初始化一個NSArray *testArray = @[@"1",@"2",@"3"]; 然后我們直接testArray[0]就可以拿到@"1";我們發(fā)現(xiàn)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`類在分類中實現(xiàn)了`objectAtIndexedSubscript`這個方法猾编。
- 神奇的
RACTupleUnpack
和RACTuplePack
宏
代碼追蹤可以知道RACTuplePack
這個宏其實就是
```
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
```
這個倒沒什么好說的,上面已經(jīng)解釋過了升敲,就是便利初始化方法
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<UITouch *> *)touches withEvent:(UIEvent *)event {
[self testTupleDesign:@[]];
}
我們都知道如果這樣寫的話,當點擊view的時候就會產(chǎn)生crash驴党,控制臺會輸出
-[__NSArray0 setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x61000001b150
相信這種錯誤有一定開發(fā)經(jīng)驗的人并不陌生
setObject:forKeyedSubscript:
我們再來看看RACTupleUnpackingTrampoline
的實現(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];
}];
}
好吧它重寫了這個方法
4 實現(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];
}
關于這個協(xié)議的上篇已經(jīng)有了詳細的解釋
二. RACSequence底層實現(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個協(xié)議港庄;既然是一個集合必定會有元素设江,
head
表示的是第一個元素;tail
表示的是尾部攘轩,不過返回值還是RACSequence
有點鏈式的意思叉存,老套路了,通過一直返回當前對象達到鏈式調(diào)用的目的度帮;-
array
返回RACSequence
對象里面裝著的所有對象歼捏;- (NSArray *)array { NSMutableArray *array = [NSMutableArray array]; for (id obj in self) { [array addObject:obj]; } return [array copy];
}
```
之所以能夠for...in...是因為實現(xiàn)了`NSFastEnumeration`協(xié)議,詳細請看[這篇](http://www.reibang.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;
}
細心的同學肯定發(fā)現(xiàn)了還有一個方法
+ (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
是如何實現(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;
//如果當前對象的head為空表示沒有數(shù)據(jù)了返回
if (value == nil) {
stop = YES;
return nil;
}
//執(zhí)行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,并沒有調(diào)用block,那么哪里會調(diào)用到這個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的時候腺晾,就會調(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
前也會調(diào)用dependencyBlock辜贵。
我們再回到bind
方法
headBlock是入?yún)閕d悯蝉,直接返回passthroughSequence的head,并不使用入?yún)ⅰ?/p>
tailBlock是入?yún)閕d托慨,返回值為RACSequence鼻由。由于RACSequence的定義類似遞歸定義的,所以tailBlock會再次遞歸調(diào)用bind:passingThroughValuesFromSequence:產(chǎn)生一個RACSequence作為新的sequence的tail供下次調(diào)用。
dependencyBlock就是成為了headBlock和tailBlock閉包執(zhí)行之前要執(zhí)行的閉包蕉世。
dependencyBlock的目的是為了把原來的sequence里面的值蔼紧,都進行一次變換。current是入?yún)assthroughSequence狠轻,valuesSeq就是原sequence的引用歉井。每次循環(huán)一次就取出原sequence的頭,直到取不到為止哈误,就是遍歷完成。
取出valuesSeq的head躏嚎,傳入bindBlock( )閉包進行變換蜜自,返回值是一個current 的sequence。在每次headBlock和tailBlock之前都會調(diào)用這個dependencyBlock卢佣,變換后新的sequence的head就是current的head重荠,新的sequence的tail就是遞歸調(diào)用傳入的current.tail。
上面是當需要使用block的時候再去調(diào)用虚茶,RACSequence
還有另一種bind
的實現(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];
}
可以看出這個地方直接就調(diào)用了bindBlock
。
通過一個例子說明兩種bind
的區(qū)別
- (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]
方法的內(nèi)部會調(diào)用.head,因為實現(xiàn)了NSFastEnumeration
,for循環(huán)實際是自定義的循環(huán)會走到- (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
對象罩扇,從而達到及時調(diào)用的目的婆芦。
可以通過signal
方法將RACSquence
和RACSignal
關聯(lián)上