前言
在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…