前言
在OOP的世界裡使用FRP的思想來程式設計,光有函式這種一等公民,還是無法滿足我們一些需求的。因此還是需要引用變數來完成各式各樣的類的操作行為。
在前幾篇文章中詳細的分析了RACStream中RACSignal的底層實現。RACStream還有另外一個子類,RACSequence,這個類是RAC專門為集合而設計的。這篇文章就專門分析一下RACSequence的底層實現。
目錄
- 1.RACTuple底層實現分析
- 2.RACSequence底層實現分析
- 3.RACSequence操作實現分析
- 4.RACSequence的一些擴充套件
一. RACTuple底層實現分析
在分析RACSequence之前,先來看看RACTuple的實現。RACTuple是ReactiveCocoa的元組類。
1. RACTuple
@interface RACTuple : NSObject <NSCoding, NSCopying, NSFastEnumeration>
@property (nonatomic, readonly) NSUInteger count;
@property (nonatomic, readonly) id first;
@property (nonatomic, readonly) id second;
@property (nonatomic, readonly) id third;
@property (nonatomic, readonly) id fourth;
@property (nonatomic, readonly) id fifth;
@property (nonatomic, readonly) id last;
@property (nonatomic, strong) NSArray *backingArray;
@property (nonatomic, copy, readonly) RACSequence *rac_sequence; // 這個是專門為sequence提供的一個擴充套件
@end複製程式碼
RACTuple的定義看上去很簡單,底層實質就是一個NSArray,只不過封裝了一些方法。RACTuple繼承了NSCoding, NSCopying, NSFastEnumeration這三個協議。
- (id)initWithCoder:(NSCoder *)coder {
self = [self init];
if (self == nil) return nil;
self.backingArray = [coder decodeObjectForKey:@keypath(self.backingArray)];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
if (self.backingArray != nil) [coder encodeObject:self.backingArray forKey:@keypath(self.backingArray)];
}複製程式碼
這裡是NSCoding協議。都是對內部的backingArray進行decodeObjectForKey:和encodeObject: 。
- (instancetype)copyWithZone:(NSZone *)zone {
// we're immutable, bitches! <---這裡是原作者的註釋
return self;
}複製程式碼
上面這是NSCopying協議。由於內部是基於NSArray的,所以是immutable不可變的。
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
}複製程式碼
上面是NSFastEnumeration協議,快速列舉也都是針對NSArray進行的操作。
// 三個類方法
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert;
+ (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
- (id)objectAtIndex:(NSUInteger)index;
- (NSArray *)allObjects;
- (instancetype)tupleByAddingObject:(id)obj;複製程式碼
RACTuple的方法也不多,總共就6個方法,3個類方法,3個例項方法。
先看類方法:
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}
+ (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;
}複製程式碼
先看這兩個類方法,這兩個類方法的區別在於是否把NSNull轉換成RACTupleNil型別。根據入參array初始化RACTuple內部的NSArray。
RACTuplePack( ) 和 RACTuplePack_( )這兩個巨集的實現也是呼叫了tupleWithObjectsFromArray:方法
#define RACTuplePack(...) \
RACTuplePack_(__VA_ARGS__)
#define RACTuplePack_(...) \
([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])複製程式碼
這裡需要注意的是RACTupleNil
+ (RACTupleNil *)tupleNil {
static dispatch_once_t onceToken;
static RACTupleNil *tupleNil = nil;
dispatch_once(&onceToken, ^{
tupleNil = [[self alloc] init];
});
return tupleNil;
}複製程式碼
RACTupleNil是一個單例。
重點需要解釋的是另外一種類方法:
+ (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;
}複製程式碼
這個類方法的引數是可變引數型別。由於用到了可變引數型別,所以就會用到va_list,va_start,va_arg,va_end。
#ifndef _VA_LIST_T
#define _VA_LIST_T
typedef __darwin_va_list va_list;
#endif /* _VA_LIST_T */
#ifndef _VA_LIST
typedef __builtin_va_list va_list;
#define _VA_LIST
#endif
#define va_start(ap, param) __builtin_va_start(ap, param)
#define va_end(ap) __builtin_va_end(ap)
#define va_arg(ap, type) __builtin_va_arg(ap, type)複製程式碼
- va_list用於宣告一個變數,我們知道函式的可變引數列表其實就是一個字串,所以va_list才被宣告為字元型指標,這個型別用於宣告一個指向引數列表的字元型指標變數,例如:va_list ap;//ap:arguement pointer
- va_start(ap,v),它的第一個引數是指向可變引數字串的變數,第二個引數是可變引數函式的第一個引數,通常用於指定可變引數列表中引數的個數。
- va_arg(ap,t),它的第一個引數指向可變引數字串的變數,第二個引數是可變引數的型別。
- va_end(ap) 用於將存放可變引數字串的變數清空(賦值為NULL)。
剩下的3個例項方法都是對陣列的操作,沒有什麼難度。
一般使用用兩個巨集,RACTupleUnpack( ) 用來解包,RACTuplePack( ) 用來裝包。
RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil];
RACTupleUnpack(NSString *string, NSNumber *num) = RACTuplePack(@"foo",@(5));
NSLog(@"string: %@", string);
NSLog(@"num: %@", num);
/* 上面的做法等價於下面的 */
RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil];
NSString *string = t[0];
NSNumber *num = t[1];
NSLog(@"string: %@", string);
NSLog(@"num: %@", num);複製程式碼
關於RACTuple還有2個相關的類,RACTupleUnpackingTrampoline,RACTupleSequence。
2. RACTupleUnpackingTrampoline
@interface RACTupleUnpackingTrampoline : NSObject
+ (instancetype)trampoline;
- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables;
@end複製程式碼
首先這個類是一個單例。
+ (instancetype)trampoline {
static dispatch_once_t onceToken;
static id trampoline = nil;
dispatch_once(&onceToken, ^{
trampoline = [[self alloc] init];
});
return trampoline;
}複製程式碼
RACTupleUnpackingTrampoline這個類也就只有一個作用,就是它對應的例項方法。
- (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];
}];
}複製程式碼
這個方法裡面會遍歷入引數組NSArray,然後依次取出陣列裡面每個value 的指標,用這個指標又賦值給了tuple[index]。
為了解釋清楚這個方法的作用,寫出測試程式碼:
RACTupleUnpackingTrampoline *tramp = [RACTupleUnpackingTrampoline trampoline];
NSString *string;
NSString *string1;
NSString *string2;
NSArray *array = [NSArray arrayWithObjects:[NSValue valueWithPointer:&string],[NSValue valueWithPointer:&string1],[NSValue valueWithPointer:&string2], nil];
NSLog(@"呼叫方法之前 string = %@,string1 = %@,string2 = %@",string,string1,string2);
[tramp setObject:[RACTuple tupleWithObjectsFromArray:@[(@"foo"),(@(10)),@"32323"]] forKeyedSubscript:array];
NSLog(@"呼叫方法之後 string = %@,string1 = %@,string2 = %@",string,string1,string2);複製程式碼
輸出如下:
呼叫方法之前 string = (null),string1 = (null),string2 = (null)
呼叫方法之後 string = foo,string1 = 10,string2 = 32323複製程式碼
這個函式的作用也就一清二楚了。但是平時我們是很少用到[NSValue valueWithPointer:&string]這種寫法的。究竟是什麼地方會用到這個函式呢?全域性搜尋一下,找到了用到這個的地方。
在RACTuple 中兩個非常有用的巨集:RACTupleUnpack( ) 用來解包,RACTuplePack( ) 用來裝包。RACTuplePack( )的實現在上面分析過了,實際是呼叫tupleWithObjectsFromArray:方法。那麼RACTupleUnpack( ) 的巨集是怎麼實現的呢?這裡就用到了RACTupleUnpackingTrampoline。
#define RACTupleUnpack_(...) \
metamacro_foreach(RACTupleUnpack_decl,, __VA_ARGS__) \
\
int RACTupleUnpack_state = 0; \
\
RACTupleUnpack_after: \
; \
metamacro_foreach(RACTupleUnpack_assign,, __VA_ARGS__) \
if (RACTupleUnpack_state != 0) RACTupleUnpack_state = 2; \
\
while (RACTupleUnpack_state != 2) \
if (RACTupleUnpack_state == 1) { \
goto RACTupleUnpack_after; \
} else \
for (; RACTupleUnpack_state != 1; RACTupleUnpack_state = 1) \
[RACTupleUnpackingTrampoline trampoline][ @[ metamacro_foreach(RACTupleUnpack_value,, __VA_ARGS__) ] ]複製程式碼
以上就是RACTupleUnpack( ) 具體的巨集。看上去很複雜。還是寫出測試程式碼分析分析。
RACTupleUnpack(NSString *string, NSNumber *num) = RACTuplePack(@"foo",@(10));複製程式碼
把上述的程式碼編譯之後的程式碼貼出來:
__attribute__((objc_ownership(strong))) id RACTupleUnpack284_var0;
__attribute__((objc_ownership(strong))) id RACTupleUnpack284_var1;
int RACTupleUnpack_state284 = 0;
RACTupleUnpack_after284: ;
__attribute__((objc_ownership(strong))) NSString *string = RACTupleUnpack284_var0;
__attribute__((objc_ownership(strong))) NSNumber *num = RACTupleUnpack284_var1;
if (RACTupleUnpack_state284 != 0)
RACTupleUnpack_state284 = 2;
while (RACTupleUnpack_state284 != 2)
if (RACTupleUnpack_state284 == 1) {
goto RACTupleUnpack_after284;
} else for (; RACTupleUnpack_state284 != 1; RACTupleUnpack_state284 = 1)
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack284_var0], [NSValue valueWithPointer:&RACTupleUnpack284_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);複製程式碼
轉換成這樣就比較好理解了。RACTupleUnpack_after284: 是一個標號。RACTupleUnpack_state284初始值為0,在下面while裡面有一個for迴圈,在這個迴圈裡面會進行解包操作,也就是會呼叫setObject:forKeyedSubscript:函式。
在迴圈裡面,
[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack284_var0], [NSValue valueWithPointer:&RACTupleUnpack284_var1], ] ]複製程式碼
這裡就是呼叫了[NSValue valueWithPointer:&string]的寫法。
至此,RACTupleUnpackingTrampoline這個類的作用也已明瞭,它是被作用設計出來用來實現神奇的RACTupleUnpack( ) 這個巨集。
當然RACTupleUnpackingTrampoline這個類的setObject:forKeyedSubscript:函式也可以使用,只不過要注意寫法,注意指標的型別,在NSValue裡面包裹的是valueWithPointer,(nullable const void *)pointer型別的。
3. RACTupleSequence
這個類僅僅只是名字裡面帶有Tuple而已,它其實是繼承自RACSequence。
需要分析這個類的原因是因為RACTuple裡面有一個擴充的屬性rac_sequence。
- (RACSequence *)rac_sequence {
return [RACTupleSequence sequenceWithTupleBackingArray:self.backingArray offset:0];
}複製程式碼
還是先看看RACTupleSequence的定義。
@interface RACTupleSequence : RACSequence
@property (nonatomic, strong, readonly) NSArray *tupleBackingArray;
@property (nonatomic, assign, readonly) NSUInteger offset;
+ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset;
@end複製程式碼
這個類是繼承自RACSequence,而且只有這一個類方法。
tupleBackingArray是來自於RACTuple裡面的backingArray。
+ (instancetype)sequenceWithTupleBackingArray:(NSArray *)backingArray offset:(NSUInteger)offset {
NSCParameterAssert(offset <= backingArray.count);
if (offset == backingArray.count) return self.empty;
RACTupleSequence *seq = [[self alloc] init];
seq->_tupleBackingArray = backingArray;
seq->_offset = offset;
return seq;
}複製程式碼
RACTupleSequence這個類的目的就是把Tuple轉換成Sequence。Sequence裡面的陣列就是Tuple內部的backingArray。offset從0開始。
二. RACSequence底層實現分析
@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;
@end複製程式碼
RACSequence是RACStream的子類,主要是ReactiveCocoa裡面的集合類。
先來說說關於RACSequence的一些概念。
RACSequence有兩個很重要的屬性就是head和tail。head是一個id,而tail又是一個RACSequence,這個定義有點遞迴的意味。
RACSequence *sequence = [RACSequence sequenceWithHeadBlock:^id{
return @(1);
} tailBlock:^RACSequence *{
return @[@2,@3,@4].rac_sequence;
}];
NSLog(@"sequence.head = %@ , sequence.tail = %@",sequence.head ,sequence.tail);複製程式碼
輸出:
sequence.head = 1 , sequence.tail = <RACArraySequence: 0x608000223920>{ name = , array = (
2,
3,
4
) }複製程式碼
這段測試程式碼就道出了head和tail的定義。更加詳細的描述見下圖:
上述程式碼裡面用到了RACSequence初始化的方法,具體的分析見後面。
objectEnumerator是一個快速列舉器。
@interface RACSequenceEnumerator : NSEnumerator
@property (nonatomic, strong) RACSequence *sequence;
@end複製程式碼
之所以需要實現這個,是為了更加方便的RACSequence進行遍歷。
- (id)nextObject {
id object = nil;
@synchronized (self) {
object = self.sequence.head;
self.sequence = self.sequence.tail;
}
return object;
}複製程式碼
有了這個NSEnumerator,就可以從RACSequence的head一直遍歷到tail。
- (NSEnumerator *)objectEnumerator {
RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
enumerator.sequence = self;
return enumerator;
}複製程式碼
回到RACSequence的定義裡面的objectEnumerator,這裡就是取出內部的RACSequenceEnumerator。
- (NSArray *)array {
NSMutableArray *array = [NSMutableArray array];
for (id obj in self) {
[array addObject:obj];
}
return [array copy];
}複製程式碼
RACSequence的定義裡面還有一個array,這個陣列就是返回一個NSArray,這個陣列裡面裝滿了RACSequence裡面所有的物件。這裡之所以能用for-in,是因為實現了NSFastEnumeration協議。至於for-in的效率,完全就看重寫NSFastEnumeration協議裡面countByEnumeratingWithState: objects: count: 方法裡面的執行效率了。
在分析RACSequence的for-in執行效率之前,先回顧一下NSFastEnumerationState的定義,這裡的屬性在接下來的實現中會被大量使用。
typedef struct {
unsigned long state; //可以被自定義成任何有意義的變數
id __unsafe_unretained _Nullable * _Nullable itemsPtr; //返回物件陣列的首地址
unsigned long * _Nullable mutationsPtr; //指向會隨著集合變動而變化的一個值
unsigned long extra[5]; //可以被自定義成任何有意義的陣列
} NSFastEnumerationState;複製程式碼
接下來要分析的這個函式的入參,stackbuf是為for-in提供的物件陣列,len是該陣列的長度。
- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(__unsafe_unretained id *)stackbuf count:(NSUInteger)len {
// 定義完成時候的狀態為state = ULONG_MAX
if (state->state == ULONG_MAX) {
return 0;
}
// 由於我們需要遍歷sequence多次,所以這裡定義state欄位來記錄sequence的首地址
RACSequence *(^getSequence)(void) = ^{
return (__bridge RACSequence *)(void *)state->state;
};
void (^setSequence)(RACSequence *) = ^(RACSequence *sequence) {
// 釋放老的sequence
CFBridgingRelease((void *)state->state);
// 保留新的sequence,把sequence的首地址存放入state中
state->state = (unsigned long)CFBridgingRetain(sequence);
};
void (^complete)(void) = ^{
// 釋放sequence,並把state置為完成態
setSequence(nil);
state->state = ULONG_MAX;
};
// state == 0是第一次呼叫時候的初始值
if (state->state == 0) {
// 在遍歷過程中,如果Sequence不再發生變化,那麼就讓mutationsPtr指向一個定值,指向extra陣列的首地址
state->mutationsPtr = state->extra;
// 再次重新整理state的值
setSequence(self);
}
// 將會把返回的物件放進stackbuf中,因此用itemsPtr指向它
state->itemsPtr = stackbuf;
NSUInteger enumeratedCount = 0;
while (enumeratedCount < len) {
RACSequence *seq = getSequence();
// 由於sequence可能是懶載入生成的,所以需要防止在遍歷器enumerator遍歷到它們的時候被釋放了
__autoreleasing id obj = seq.head;
// 沒有頭就結束遍歷
if (obj == nil) {
complete();
break;
}
// 遍歷sequence,每次取出來的head都放入stackbuf陣列中。
stackbuf[enumeratedCount++] = obj;
// 沒有尾就是完成遍歷
if (seq.tail == nil) {
complete();
break;
}
// 取出tail以後,這次遍歷結束的tail,即為下次遍歷的head,設定seq.tail為Sequence的head,為下次迴圈做準備
setSequence(seq.tail);
}
return enumeratedCount;
}複製程式碼
整個遍歷的過程類似遞迴的過程,從頭到尾依次遍歷一遍。
再來研究研究RACSequence的初始化:
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock;
+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}複製程式碼
初始化RACSequence,會呼叫RACDynamicSequence。這裡有點類比RACSignal的RACDynamicSignal。
再來看看RACDynamicSequence的定義。
@interface RACDynamicSequence () {
id _head;
RACSequence *_tail;
id _dependency;
}
@property (nonatomic, strong) id headBlock;
@property (nonatomic, strong) id tailBlock;
@property (nonatomic, assign) BOOL hasDependency;
@property (nonatomic, strong) id (^dependencyBlock)(void);
@end複製程式碼
這裡需要說明的是此處的headBlock,tailBlock,dependencyBlock的修飾符都是用了strong,而不是copy。這裡是一個很奇怪的bug導致的。在github.com/ReactiveCoc…中詳細記錄了用copy關鍵字會導致記憶體洩露的bug。具體程式碼如下:
[[[@[@1,@2,@3,@4,@5] rac_sequence] filter:^BOOL(id value) {
return [value intValue] > 1;
}] array];複製程式碼
最終發現這個問題的人把copy改成strong就神奇的修復了這個bug。最終整個ReactiveCocoa庫裡面就只有這裡把block的關鍵字從copy改成了strong,而不是所有的地方都改成strong。
原作者Justin Spahr-Summers大神對這個問題的最終解釋是:
Maybe there's just something weird with how we override dealloc, set the blocks from a class method, cast them, or something else.
所以日常我們寫block的時候,沒有特殊情況,依舊需要繼續用copy進行修飾。
+ (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;
}複製程式碼
hasDependency這個變數是代表是否有dependencyBlock。這個函式裡面就只把headBlock和tailBlock儲存起來了。
+ (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;
}複製程式碼
另外一個類方法sequenceWithLazyDependency: headBlock: tailBlock:是帶有dependencyBlock的,這個方法裡面會儲存headBlock,tailBlock,dependencyBlock這3個block。
從RACSequence這兩個唯一的初始化方法之間就引出了RACSequence兩大核心問題之一,積極運算 和 惰性求值。
1. 積極運算 和 惰性求值
在RACSequence的定義中還有兩個RACSequence —— eagerSequence 和 lazySequence。這兩個RACSequence就是分別對應著積極運算的RACSequence和惰性求值的RACSequence。
關於這兩個概念最最新形象的比喻還是臧老師部落格裡面的這篇文章聊一聊iOS開發中的惰性計算裡面寫的一段笑話。引入如下:
有一隻小白兔,跑到蔬菜店裡問老闆:“老闆,有100個胡蘿蔔嗎?”。老闆說:“沒有那麼多啊。”,小白兔失望的說道:“哎,連100個胡蘿蔔都沒有。。。”。第二天小白兔又來到蔬菜店問老闆:“今天有100個胡蘿蔔了吧?”,老闆尷尬的說:“今天還是缺點,明天就能好了。”,小白兔又很失望的走了。第三天小白兔剛一推門,老闆就高興的說道:“有了有了,從前天就進貨的100個胡蘿蔔到貨了。”,小白兔說:“太好了,我要買2根!”。。。
如果日常我們遇到了這種問題,就很浪費記憶體空間了。比如在記憶體裡面開了一個100W大小的陣列,結果實際只使用到100個數值。這個時候就需要用到惰性運算了。
在RACSequence裡面這兩種方式都支援,我們來看看底層原始碼是如何實現的。
先來看看平時我們很熟悉的情況——積極運算。
在RACSequence中積極運算的代表是RACSequence的一個子類RACArraySequence的子類——RACEagerSequence。它的積極運算表現在其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];
}複製程式碼
從上述程式碼中能看到主要是進行了2層迴圈,最外層迴圈遍歷的自己RACSequence中的值,然後拿到這個值傳入閉包bindBlock( )中,返回一個RACSequence,最後用一個NSMutableArray依次把每個RACSequence裡面的值都裝起來。
第二個for-in迴圈是在遍歷RACSequence,之所以可以用for-in的方式遍歷就是因為實現了NSFastEnumeration協議,實現了countByEnumeratingWithState: objects: count: 方法,這個方法在上面詳細分析過了,這裡不再贅述。
這裡就是一個積極運算的例子,在每次迴圈中都會把閉包block( )的值計算出來。值得說明的是,最後返回的RACSequence的型別是self.class型別的,即還是RACEagerSequence型別的。
再來看看RACSequence中的惰性求值是怎麼實現的。
在RACSequence中,bind函式是下面這個樣子:
- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
RACStreamBindBlock bindBlock = block();
return [[self bind:bindBlock passingThroughValuesFromSequence:nil] setNameWithFormat:@"[%@] -bind:", self.name];
}複製程式碼
實際上呼叫了bind: passingThroughValuesFromSequence:方法,第二個入參傳入nil。
- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
__block RACSequence *valuesSeq = self;
__block RACSequence *current = passthroughSequence;
__block BOOL stop = NO;
RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
// 暫時省略
} 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: passingThroughValuesFromSequence:方法的實現中,就是用sequenceWithLazyDependency: headBlock: tailBlock:方法生成了一個RACSequence,並返回。在sequenceWithLazyDependency: headBlock: tailBlock:上面分析過原始碼,主要目的是為了儲存3個閉包,headBlock,tailBlock,dependencyBlock。
通過呼叫RACSequence裡面的bind操作,並沒有執行3個閉包裡面的值,只是儲存起來了。這裡就是惰性求值的表現——等到要用的時候才會計算。
通過上述原始碼的分析,可以寫出如下的測試程式碼加深理解。
NSArray *array = @[@1,@2,@3,@4,@5];
RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
NSLog(@"lazySequence");
return @(101);
}];
RACSequence *eagerSequence = [array.rac_sequence.eagerSequence map:^id(id value) {
NSLog(@"eagerSequence");
return @(100);
}];複製程式碼
上述程式碼執行之後,會輸出如下資訊:
eagerSequence
eagerSequence
eagerSequence
eagerSequence
eagerSequence複製程式碼
只輸出了5遍eagerSequence,lazySequence並沒有輸出。原因是因為bind閉包只在eagerSequence中真正被呼叫執行了,而在lazySequence中bind閉包僅僅只是被copy了。
那如何讓lazySequence執行bind閉包呢?
[lazySequence array];複製程式碼
通過執行上述程式碼,就可以輸出5遍“lazySequence”了。因為bind閉包再次會被呼叫執行。
積極運算 和 惰性求值在這裡就區分出來了。在RACSequence中,除去RACEagerSequence只積極運算,其他的Sequence都是惰性求值的。
接下來再繼續分析RACSequence是如何實現惰性求值的。
RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
while (current.head == nil) {
if (stop) return nil;
// 遍歷當前sequence,取出下一個值
id value = valuesSeq.head;
if (value == nil) {
// 遍歷完sequence所有的值
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];
}];複製程式碼
在bind操作中建立了這樣一個lazySequence,3個block閉包儲存瞭如何建立一個lazySequence的做法。
headBlock是入參為id,返回值也是一個id。在建立lazySequence的head的時候,並不關心入參,直接返回passthroughSequence的head。
tailBlock是入參為id,返回值為RACSequence。由於RACSequence的定義類似遞迴定義的,所以tailBlock會再次遞迴呼叫bind:passingThroughValuesFromSequence:產生一個RACSequence作為新的sequence的tail。
dependencyBlock的返回值是作為headBlock和tailBlock的入參。不過現在headBlock和tailBlock都不關心這個入參。那麼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。
RACDynamicSequence建立的lazyDependency的過程就是儲存了3個block的過程。那這些閉包什麼時候會被呼叫呢?
- (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;
}
}複製程式碼
上面的原始碼就是獲取RACDynamicSequence中head的實現。當要取出sequence的head的時候,就會呼叫headBlock( )。如果儲存了dependencyBlock閉包,在執行headBlock( )之前會先執行dependencyBlock( )進行一次變換。
- (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;
}
}複製程式碼
獲取RACDynamicSequence中tail的時候,和獲取head是一樣的,當需要取出tail的時候才會呼叫tailBlock( )。當有dependencyBlock閉包,會先執行dependencyBlock閉包,再呼叫tailBlock( )。
總結一下:
RACSequence的惰性求值,除去RACEagerSequence的bind函式以外,其他所有的Sequence都是基於惰性求值的。只有到取出來運算之前才會去把相應的閉包執行一遍。
在RACSequence所有函式中,只有bind函式會傳入dependencyBlock( )閉包,(RACEagerSequence會重寫這個bind函式),所以看到dependencyBlock( )閉包一定可以推斷出是RACSequence做了變換操作了。
2. Pull-driver 和 Push-driver
在RACSequence中有一個方法可以讓RACSequence和RACSignal進行關聯上。
- (RACSignal *)signal {
return [[self signalWithScheduler:[RACScheduler scheduler]] setNameWithFormat:@"[%@] -signal", self.name];
}
- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler {
return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
__block RACSequence *sequence = self;
return [scheduler scheduleRecursiveBlock:^(void (^reschedule)(void)) {
if (sequence.head == nil) {
[subscriber sendCompleted];
return;
}
[subscriber sendNext:sequence.head];
sequence = sequence.tail;
reschedule();
}];
}] setNameWithFormat:@"[%@] -signalWithScheduler: %@", self.name, scheduler];
}複製程式碼
RACSequence中的signal方法會呼叫signalWithScheduler:方法。在signalWithScheduler:方法中會建立一個新的訊號。這個新的訊號的RACDisposable訊號由scheduleRecursiveBlock:產生。
- (void)scheduleRecursiveBlock:(RACSchedulerRecursiveBlock)recursiveBlock addingToDisposable:(RACCompoundDisposable *)disposable {
@autoreleasepool {
RACCompoundDisposable *selfDisposable = [RACCompoundDisposable compoundDisposable];
[disposable addDisposable:selfDisposable];
__weak RACDisposable *weakSelfDisposable = selfDisposable;
RACDisposable *schedulingDisposable = [self schedule:^{
if (disposable.disposed) return;
void (^reallyReschedule)(void) = ^{
if (disposable.disposed) return;
// 這裡是遞迴
[self scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable];
};
// 這裡實際上不需要__block關鍵字,但是由於Clang編譯器的特性,為了保護下面的變數,所以加上了__block關鍵字
__block NSLock *lock = [[NSLock alloc] init];
lock.name = [NSString stringWithFormat:@"%@ %s", self, sel_getName(_cmd)];
__block NSUInteger rescheduleCount = 0;
// 一旦同步操作執行完成,rescheduleImmediately就應該被設為YES
__block BOOL rescheduleImmediately = NO;
@autoreleasepool {
recursiveBlock(^{
[lock lock];
BOOL immediate = rescheduleImmediately;
if (!immediate) ++rescheduleCount;
[lock unlock];
if (immediate) reallyReschedule();
});
}
[lock lock];
NSUInteger synchronousCount = rescheduleCount;
rescheduleImmediately = YES;
[lock unlock];
for (NSUInteger i = 0; i < synchronousCount; i++) {
reallyReschedule();
}
}];
[selfDisposable addDisposable:schedulingDisposable];
}
}複製程式碼
這段程式碼雖然長,但是拆分分析一下:
__block NSUInteger rescheduleCount = 0;
// 一旦同步操作執行完成,rescheduleImmediately就應該被設為YES
__block BOOL rescheduleImmediately = NO;複製程式碼
rescheduleCount 是遞迴次數計數。rescheduleImmediately這個BOOL是決定是否立即執行reallyReschedule( )閉包。
recursiveBlock是入參,它實際是下面這段閉包程式碼:
{
if (sequence.head == nil) {
[subscriber sendCompleted];
return;
}
[subscriber sendNext:sequence.head];
sequence = sequence.tail;
reschedule();
}複製程式碼
recursiveBlock的入參是reschedule( )。執行完上面的程式碼之後開始執行入參reschedule( )的程式碼,入參reschedule( 閉包的程式碼是如下:
^{
[lock lock];
BOOL immediate = rescheduleImmediately;
if (!immediate) ++rescheduleCount;
[lock unlock];
if (immediate) reallyReschedule();
}複製程式碼
在這段block中會統計rescheduleCount,如果rescheduleImmediately為YES還會繼續開始執行遞迴操作reallyReschedule( )。
for (NSUInteger i = 0; i < synchronousCount; i++) {
reallyReschedule();
}複製程式碼
最終會在這個迴圈裡面遞迴呼叫reallyReschedule( )閉包。reallyReschedule( )閉包執行的操作就是再次執行scheduleRecursiveBlock:recursiveBlock addingToDisposable:disposable方法。
每次執行一次遞迴就會取出sequence的head值傳送出來,直到sequence.head = = nil傳送完成訊號。
既然RACSequence也可以轉換成RACSignal,那麼就需要總結一下兩者的異同點。
總結一下:
RACSequence 和 RACSignal 異同點對比:
- RACSequence除去RACEagerSequence,其他所有的都是基於惰性計算的,這和RACSignal是一樣的。
- RACSequence是在時間上是連續的,一旦把RACSequence變成signal,再訂閱,會立即把所有的值一口氣都傳送出來。RACSignal是在時間上是離散的,當有事件到來的時候,才會傳送出資料流。
- RACSequence是Pull-driver,由訂閱者來決定是否傳送值,只要訂閱者訂閱了,就會傳送資料流。RACSignal是Push-driver,它傳送資料流是不由訂閱者決定的,不管有沒有訂閱者,它有離散事件產生了,就會傳送資料流。
- RACSequence傳送的全是資料,RACSignal傳送的全是事件。事件不僅僅包括資料,還包括事件的狀態,比如說事件是否出錯,事件是否完成。
三. RACSequence操作實現分析
RACSequence還有以下幾個操作。
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce;
- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce;
- (BOOL)any:(BOOL (^)(id value))block;
- (BOOL)all:(BOOL (^)(id value))block;
- (id)objectPassingTest:(BOOL (^)(id value))block;複製程式碼
1. foldLeftWithStart: reduce:
- (id)foldLeftWithStart:(id)start reduce:(id (^)(id, id))reduce {
NSCParameterAssert(reduce != NULL);
if (self.head == nil) return start;
for (id value in self) {
start = reduce(start, value);
}
return start;
}複製程式碼
這個函式傳入了一個初始值start,然後依次迴圈執行reduce( ),迴圈之後,最終的值作為返回值返回。這個函式就是摺疊函式,從左邊摺疊到右邊。
2. foldRightWithStart: reduce:
- (id)foldRightWithStart:(id)start reduce:(id (^)(id, RACSequence *))reduce {
NSCParameterAssert(reduce != NULL);
if (self.head == nil) return start;
RACSequence *rest = [RACSequence sequenceWithHeadBlock:^{
return [self.tail foldRightWithStart:start reduce:reduce];
} tailBlock:nil];
return reduce(self.head, rest);
}複製程式碼
這個函式和上一個foldLeftWithStart: reduce:是一樣的,只不過方向是從右往左。
3. objectPassingTest:
- (id)objectPassingTest:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self filter:block].head;
}複製程式碼
objectPassingTest:裡面會呼叫RACStream中的filter:函式,這個函式在前幾篇文章分析過了。如果block(value)為YES,就代表通過了Test,那麼就會返回value的sequence。取出head返回。
4. any:
- (BOOL)any:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
return [self objectPassingTest:block] != nil;
}複製程式碼
any:會呼叫objectPassingTest:函式,如果不為nil就代表有value值通過了Test,有通過了value的就返回YES,反之返回NO。
5. all:
- (BOOL)all:(BOOL (^)(id))block {
NSCParameterAssert(block != NULL);
NSNumber *result = [self foldLeftWithStart:@YES reduce:^(NSNumber *accumulator, id value) {
return @(accumulator.boolValue && block(value));
}];
return result.boolValue;
}複製程式碼
all:會從左往右依次對每個值進行block( ) Test,然後每個值依次進行&&操作。
6. concat:
- (instancetype)concat:(RACStream *)stream {
NSCParameterAssert(stream != nil);
return [[[RACArraySequence sequenceWithArray:@[ self, stream ] offset:0]
flatten]
setNameWithFormat:@"[%@] -concat: %@", self.name, stream];
}複製程式碼
concat:的操作和RACSignal的作用是一樣的。它會把原sequence和入參stream連線到一起,組合成一個高階sequence,最後呼叫flatten“拍扁”。關於flatten的實現見前幾篇RACStream裡面的flatten實現分析。
7. zipWith:
- (instancetype)zipWith:(RACSequence *)sequence {
NSCParameterAssert(sequence != nil);
return [[RACSequence
sequenceWithHeadBlock:^ id {
if (self.head == nil || sequence.head == nil) return nil;
return RACTuplePack(self.head, sequence.head);
} tailBlock:^ id {
if (self.tail == nil || [[RACSequence empty] isEqual:self.tail]) return nil;
if (sequence.tail == nil || [[RACSequence empty] isEqual:sequence.tail]) return nil;
return [self.tail zipWith:sequence.tail];
}]
setNameWithFormat:@"[%@] -zipWith: %@", self.name, sequence];
}複製程式碼
由於sequence的定義是遞迴形式的,所以zipWith:也是遞迴來進行的。zipWith:新的sequence的head是原來2個sequence的head組合成RACTuplePack。新的sequence的tail是原來2個sequence的tail遞迴呼叫zipWith:。
四. RACSequence的一些擴充套件
關於RACSequence有以下9個子類,其中RACEagerSequence是繼承自RACArraySequence。這些子類看名字就知道sequence裡面裝的是什麼型別的資料。RACUnarySequence裡面裝的是單元sequence。它只有head值,沒有tail值。
RACSequenceAdditions 總共有7個Category。這7個Category分別對iOS 裡面的集合類進行了RACSequence的擴充套件,使我們能更加方便的使用RACSequence。
1. NSArray+RACSequenceAdditions
@interface NSArray (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSArray陣列轉換成RACSequence。
- (RACSequence *)rac_sequence {
return [RACArraySequence sequenceWithArray:self offset:0];
}複製程式碼
根據NSArray建立一個RACArraySequence並返回。
2. NSDictionary+RACSequenceAdditions
@interface NSDictionary (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@property (nonatomic, copy, readonly) RACSequence *rac_keySequence;
@property (nonatomic, copy, readonly) RACSequence *rac_valueSequence;
@end複製程式碼
這個Category能把任意一個NSDictionary字典轉換成RACSequence。
- (RACSequence *)rac_sequence {
NSDictionary *immutableDict = [self copy];
return [immutableDict.allKeys.rac_sequence map:^(id key) {
id value = immutableDict[key];
return RACTuplePack(key, value);
}];
}
- (RACSequence *)rac_keySequence {
return self.allKeys.rac_sequence;
}
- (RACSequence *)rac_valueSequence {
return self.allValues.rac_sequence;
}複製程式碼
rac_sequence會把字典都轉化為一個裝滿RACTuplePack的RACSequence,在這個RACSequence中,第一個位置是key,第二個位置是value。
rac_keySequence是裝滿所有key的RACSequence。
rac_valueSequence是裝滿所有value的RACSequence。
3. NSEnumerator+RACSequenceAdditions
@interface NSEnumerator (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSEnumerator轉換成RACSequence。
- (RACSequence *)rac_sequence {
return [RACSequence sequenceWithHeadBlock:^{
return [self nextObject];
} tailBlock:^{
return self.rac_sequence;
}];
}複製程式碼
返回的RACSequence的head是當前的sequence的head,tail就是當前的sequence。
4. NSIndexSet+RACSequenceAdditions
@interface NSIndexSet (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSIndexSet轉換成RACSequence。
- (RACSequence *)rac_sequence {
return [RACIndexSetSequence sequenceWithIndexSet:self];
}
+ (instancetype)sequenceWithIndexSet:(NSIndexSet *)indexSet {
NSUInteger count = indexSet.count;
if (count == 0) return self.empty;
NSUInteger sizeInBytes = sizeof(NSUInteger) * count;
NSMutableData *data = [[NSMutableData alloc] initWithCapacity:sizeInBytes];
[indexSet getIndexes:data.mutableBytes maxCount:count inIndexRange:NULL];
RACIndexSetSequence *seq = [[self alloc] init];
seq->_data = data;
seq->_indexes = data.bytes;
seq->_count = count;
return seq;
}複製程式碼
返回RACIndexSetSequence,在這個IndexSetSequence中,data裡面裝的NSData,indexes裡面裝的NSUInteger,count裡面裝的是index的總數。
5. NSOrderedSet+RACSequenceAdditions
@interface NSOrderedSet (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSOrderedSet轉換成RACSequence。
- (RACSequence *)rac_sequence {
return self.array.rac_sequence;
}複製程式碼
返回的NSOrderedSet中的陣列轉換成sequence。
6. NSSet+RACSequenceAdditions
@interface NSSet (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSSet轉換成RACSequence。
- (RACSequence *)rac_sequence {
return self.allObjects.rac_sequence;
}複製程式碼
根據NSSet的allObjects陣列建立一個RACArraySequence並返回。
7. NSString+RACSequenceAdditions
@interface NSString (RACSequenceAdditions)
@property (nonatomic, copy, readonly) RACSequence *rac_sequence;
@end複製程式碼
這個Category能把任意一個NSString轉換成RACSequence。
- (RACSequence *)rac_sequence {
return [RACStringSequence sequenceWithString:self offset:0];
}複製程式碼
返回的是一個裝滿string字元的陣列對應的sequence。
最後
關於RACSequence 和 RACTuple底層實現分析都已經分析完成。最後請大家多多指教。
本次徵文活動的連結:
gold.xitu.io/post/58522d…