ReactiveCocoa 中 集合類 RACSequence 和 RACTuple 底層實現分析 | 掘金技術徵文

前言

在OOP的世界裏使用FRP的思想來編程,光有函數這種一等公民,仍是沒法知足咱們一些需求的。所以仍是須要引用變量來完成各式各樣的類的操做行爲。ios

在前幾篇文章中詳細的分析了RACStream中RACSignal的底層實現。RACStream還有另一個子類,RACSequence,這個類是RAC專門爲集合而設計的。這篇文章就專門分析一下RACSequence的底層實現。git

目錄

  • 1.RACTuple底層實現分析
  • 2.RACSequence底層實現分析
  • 3.RACSequence操做實現分析
  • 4.RACSequence的一些擴展

一. RACTuple底層實現分析

在分析RACSequence以前,先來看看RACTuple的實現。RACTuple是ReactiveCocoa的元組類。github

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: 。vim

- (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個實例方法。jsp

先看類方法:ide

+ (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)複製代碼
  1. va_list用於聲明一個變量,咱們知道函數的可變參數列表其實就是一個字符串,因此va_list才被聲明爲字符型指針,這個類型用於聲明一個指向參數列表的字符型指針變量,例如:va_list ap;//ap:arguement pointer
  2. va_start(ap,v),它的第一個參數是指向可變參數字符串的變量,第二個參數是可變參數函數的第一個參數,一般用於指定可變參數列表中參數的個數。
  3. va_arg(ap,t),它的第一個參數指向可變參數字符串的變量,第二個參數是可變參數的類型。
  4. 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( )。

總結一下:

  1. RACSequence的惰性求值,除去RACEagerSequence的bind函數之外,其餘全部的Sequence都是基於惰性求值的。只有到取出來運算以前纔會去把相應的閉包執行一遍。

  2. 在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 異同點對比:

  1. RACSequence除去RACEagerSequence,其餘全部的都是基於惰性計算的,這和RACSignal是同樣的。
  2. RACSequence是在時間上是連續的,一旦把RACSequence變成signal,再訂閱,會當即把全部的值一口氣都發送出來。RACSignal是在時間上是離散的,當有事件到來的時候,纔會發送出數據流。
  3. RACSequence是Pull-driver,由訂閱者來決定是否發送值,只要訂閱者訂閱了,就會發送數據流。RACSignal是Push-driver,它發送數據流是不禁訂閱者決定的,無論有沒有訂閱者,它有離散事件產生了,就會發送數據流。
  4. 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…

相關文章
相關標籤/搜索