ReactiveCocoa 中 集合類 RACTuple/RACSequence

ReactiveCocoa 中 集合類 RACTuple/RACSequence

本篇目錄
1. 解釋一下`RACTuple`中的一些難點俭正;
2. RACSequence底層實現(xiàn)分析歹啼。

一. RACTuple

  1. 先看初始化方法
    //1.
  + (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
  return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
  }
  // 2.
 + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:    (BOOL)convert {
  RACTuple *tuple = [[self alloc] init];
  
  if (convert) {
      NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:array.count];
      for (id object in array) {
          [newArray addObject:(object == NSNull.null ? RACTupleNil.tupleNil : object)];
      }
      
      tuple.backingArray = newArray;
  } else {
      tuple.backingArray = [array copy];
  }
  
  return tuple;
}
//3.
 + (instancetype)tupleWithObjects:(id)object, ... {
  RACTuple *tuple = [[self alloc] init];

  va_list args;
  va_start(args, object);

  NSUInteger count = 0;
  for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
      ++count;
  }
  va_end(args);
  if (count == 0) {
      tuple.backingArray = @[];
      return tuple;
  }
  NSMutableArray *objects = [[NSMutableArray alloc] initWithCapacity:count];
  va_start(args, object);
  for (id currentObject = object; currentObject != nil; currentObject = va_arg(args, id)) {
      [objects addObject:currentObject];
  }

  va_end(args);
  
  tuple.backingArray = objects;
  return tuple;
}
  1. 1和2其實是同一個方法,實現(xiàn)很簡單罐农,其實就是初始化一個RACTuple類型的對象,至于為什么使用[[self alloc] init],而不使用[[RACTuple alloc] init]是為了當RACTuple子類調(diào)用的時候可以直接初始化子類贝乎,根據(jù)convert入?yún)⒌牟煌瑏砼袛鄬?shù)組的內(nèi)部是否的Null對象替換成RACTupleNil.tupleNil叽粹,很簡單不對說了览效;
  2. 我們看下方法3,
  1. va_list用于聲明一個變量虫几,我們知道函數(shù)的可變參數(shù)列表其實就是一個字符串锤灿,所以va_list才被聲明為字符型指針,這個類型用于聲明一個指向參數(shù)列表的字符型指針變量辆脸,例如:va_list ap;//ap:arguement pointer
  2. va_start(ap,v),它的第一個參數(shù)是指向可變參數(shù)字符串的變量但校,第二個參數(shù)是可變參數(shù)函數(shù)的第一個參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個數(shù)啡氢。
  3. va_arg(ap,t),它的第一個參數(shù)指向可變參數(shù)字符串的變量状囱,第二個參數(shù)是可變參數(shù)的類型术裸。
  4. va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL)。

由上面4個引用應該沒什么問題了亭枷。

  1. 通過下標取出元素

通常OC的集合類比如NSArray袭艺、NSDictionary等等都可以直接通過下標來取出值,比如我們初始化一個NSArray *testArray = @[@"1",@"2",@"3"]; 然后我們直接testArray[0]就可以拿到@"1";我們發(fā)現(xiàn)RACTuple類也可以直接通過下標獲取

```
- (id)first {
    return self[0];
}

- (id)second {
    return self[1];
}

- (id)third {
    return self[2];
}

- (id)fourth {
    return self[3];
}

- (id)fifth {
    return self[4];
}

- (id)last {
    return self[self.count - 1];
}
```
這是怎么做到的呢叨粘,關鍵在于下面的代碼

```
@implementation RACTuple (ObjectSubscripting)

- (id)objectAtIndexedSubscript:(NSUInteger)idx {
    return [self objectAtIndex:idx];
}

@end
```
原因就是`RACTuple`類在分類中實現(xiàn)了`objectAtIndexedSubscript`這個方法猾编。
  1. 神奇的RACTupleUnpackRACTuplePack

代碼追蹤可以知道RACTuplePack這個宏其實就是

```
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array;

```
這個倒沒什么好說的,上面已經(jīng)解釋過了升敲,就是便利初始化方法

RACTupleUnpack

通過一個demo來說明這個宏到底是干什么的

        - (void)testTupleDesign {
             RACTupleUnpack(NSString *string, NSNumber *num) = RACTuplePack(@"foo",@(10)); 
        }

通過下面的方式查看編譯時的代碼

也就是下面這個

    __attribute__((objc_ownership(strong))) id RACTupleUnpack875_var0;
    __attribute__((objc_ownership(strong))) id RACTupleUnpack875_var1;
    
    int RACTupleUnpack_state875 = 0;
    RACTupleUnpack_after875: ;
    __attribute__((objc_ownership(strong))) NSString *string = RACTupleUnpack875_var0;
    __attribute__((objc_ownership(strong))) NSNumber *num = RACTupleUnpack875_var1;
    if (RACTupleUnpack_state875 != 0) RACTupleUnpack_state875 = 2;
    while (RACTupleUnpack_state875 != 2)
        if (RACTupleUnpack_state875 == 1) { goto RACTupleUnpack_after875; }
        else for (; RACTupleUnpack_state875 != 1; RACTupleUnpack_state875 = 1)
            [RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);

去掉一些干擾項其實就是

[RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ] ] = ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]]);

相當于這個

dic[@"key"] = value

對應上面

  1. dic ---> [RACTupleUnpackingTrampoline trampoline]
  2. key ---> [NSValue valueWithPointer:&RACTupleUnpack875_var0], [NSValue valueWithPointer:&RACTupleUnpack875_var1], ]
  3. value ---> ([RACTuple tupleWithObjectsFromArray:@[ (@"foo") ?: RACTupleNil.tupleNil, (@(10)) ?: RACTupleNil.tupleNil, ]])

這就很奇怪了不是一般只有NSDictionary對象才會有這種附值得嗎答倡,為什么RACTupleUnpackingTrampoline也可以呢
首先來看一下下面的demo,

- (void)testTupleDesign:(NSMutableDictionary *)dic {
   
    dic[@"key"] = @"2";
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self testTupleDesign:@[]];
}

我們都知道如果這樣寫的話,當點擊view的時候就會產(chǎn)生crash驴党,控制臺會輸出


-[__NSArray0 setObject:forKeyedSubscript:]: unrecognized selector sent to instance 0x61000001b150

相信這種錯誤有一定開發(fā)經(jīng)驗的人并不陌生

setObject:forKeyedSubscript:

我們再來看看RACTupleUnpackingTrampoline的實現(xiàn)

@interface RACTupleUnpackingTrampoline : NSObject

+ (instancetype)trampoline;
- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables;

@end

- (void)setObject:(RACTuple *)tuple forKeyedSubscript:(NSArray *)variables {
    NSCParameterAssert(variables != nil);
    
    [variables enumerateObjectsUsingBlock:^(NSValue *value, NSUInteger index, BOOL *stop) {
        __strong id *ptr = (__strong id *)value.pointerValue;
        *ptr = tuple[index];
    }];
}

好吧它重寫了這個方法

4 實現(xiàn)了NSFastEnumeration協(xié)議

    - (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained [])buffer count:(NSUInteger)len {
        return [self.backingArray countByEnumeratingWithState:state objects:buffer count:len];
    }
    

關于這個協(xié)議的上篇已經(jīng)有了詳細的解釋

二. RACSequence底層實現(xiàn)分析瘪撇。

(1) 先看看頭文件

@interface RACSequence : RACStream <NSCoding, NSCopying, NSFastEnumeration>

@property (nonatomic, strong, readonly) id head;

@property (nonatomic, strong, readonly) RACSequence *tail;

@property (nonatomic, copy, readonly) NSArray *array;

@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;

@property (nonatomic, copy, readonly) RACSequence *eagerSequence;

@property (nonatomic, copy, readonly) RACSequence *lazySequence;
  1. RACSequence是繼承RACStream的,分別遵循了3個協(xié)議港庄;

  2. 既然是一個集合必定會有元素设江,head表示的是第一個元素;

  3. tail表示的是尾部攘轩,不過返回值還是RACSequence有點鏈式的意思叉存,老套路了,通過一直返回當前對象達到鏈式調(diào)用的目的度帮;

  4. array返回RACSequence對象里面裝著的所有對象歼捏;

    - (NSArray *)array {
    NSMutableArray *array = [NSMutableArray array];
    for (id obj in self) {
        [array addObject:obj];
    }
    
    return [array copy];
    

}

```
之所以能夠for...in...是因為實現(xiàn)了`NSFastEnumeration`協(xié)議,詳細請看[這篇](http://www.reibang.com/p/7a2ffb5e171c)
  1. objectEnumerator通過她可以遍歷所有的對象

    - (NSEnumerator *)objectEnumerator {
        RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
        enumerator.sequence = self;
        return enumerator;
    }
    @interface RACSequenceEnumerator : NSEnumerator
    @property (nonatomic, strong) RACSequence *sequence;
    
    @end
    @implementation RACSequenceEnumerator
    
    - (id)nextObject {
        id object = nil;
        
        @synchronized (self) {
            object = self.sequence.head;
            self.sequence = self.sequence.tail;
        }
        
        return object;
    }
    
    
  1. eagerSequencelazySequence這個后面單獨講笨篷。

(2) 再看初始化方法

+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
    return [[RACDynamicSequence sequenceWithHeadBlock:headBlock tailBlock:tailBlock] setNameWithFormat:@"+sequenceWithHeadBlock:tailBlock:"];
}

是不是想起了RACSignal的初始化瞳秽?有點差不多的意思實際初始化的是RACSequence的子類RACDynamicSequence,實際是保存了2個block

+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock {
    NSCParameterAssert(headBlock != nil);

    RACDynamicSequence *seq = [[RACDynamicSequence alloc] init];
    seq.headBlock = [headBlock copy];
    seq.tailBlock = [tailBlock copy];
    seq.hasDependency = NO;
    return seq;
}

細心的同學肯定發(fā)現(xiàn)了還有一個方法

+ (RACSequence *)sequenceWithLazyDependency:(id (^)(void))dependencyBlock headBlock:(id (^)(id dependency))headBlock tailBlock:(RACSequence *(^)(id dependency))tailBlock {
    NSCParameterAssert(dependencyBlock != nil);
    NSCParameterAssert(headBlock != nil);

    RACDynamicSequence *seq = [[RACDynamicSequence alloc] init];
    seq.headBlock = [headBlock copy];
    seq.tailBlock = [tailBlock copy];
    seq.dependencyBlock = [dependencyBlock copy];
    seq.hasDependency = YES;
    return seq;
}

這個方法是有在bind的時候才會用到率翅,這兩個方法的不同之處就是多了個dependencyBlock练俐,我們看看RACSequencebind是如何實現(xiàn)了

- (instancetype)bind:(RACStreamBindBlock)bindBlock passingThroughValuesFromSequence:(RACSequence *)passthroughSequence {
    
    __block RACSequence *valuesSeq = self;
    __block RACSequence *current = passthroughSequence;
    __block BOOL stop = NO;

    RACSequence *sequence = [RACDynamicSequence sequenceWithLazyDependency:^ id {
        while (current.head == nil) {
            if (stop) return nil;
            
            id value = valuesSeq.head;
//如果當前對象的head為空表示沒有數(shù)據(jù)了返回
            if (value == nil) {

                stop = YES;
                return nil;
            }
//執(zhí)行bindBlock
            current = (id)bindBlock(value, &stop);
            if (current == nil) {
                stop = YES;
                return nil;
            }
//取出下一個對象賦值給valuesSeq
            valuesSeq = valuesSeq.tail;
        }

        NSCAssert([current isKindOfClass:RACSequence.class], @"-bind: block returned an object that is not a sequence: %@", current);
        return nil;
    } headBlock:^(id _) {
        return current.head;
    } tailBlock:^ id (id _) {
        if (stop) return nil;

        return [valuesSeq bind:bindBlock passingThroughValuesFromSequence:current.tail];
    }];

    sequence.name = self.name;
    return sequence;
}

這里的bind只是保存了block,并沒有調(diào)用block,那么哪里會調(diào)用到這個block呢冕臭?

我們查看RACDynamicSequenceheadtail方法可知

- (id)head {
    @synchronized (self) {
        id untypedHeadBlock = self.headBlock;
        if (untypedHeadBlock == nil) return _head;

        if (self.hasDependency) {
            if (self.dependencyBlock != nil) {
                _dependency = self.dependencyBlock();
                self.dependencyBlock = nil;
            }

            id (^headBlock)(id) = untypedHeadBlock;
            _head = headBlock(_dependency);
        } else {
            id (^headBlock)(void) = untypedHeadBlock;
            _head = headBlock();
        }

        self.headBlock = nil;
        return _head;
    }
}

要取出sequence的head的時候腺晾,就會調(diào)用headBlock( ),并且把返回值作為入?yún)ⅲ?/p>

- (RACSequence *)tail {
    @synchronized (self) {
        id untypedTailBlock = self.tailBlock;
        if (untypedTailBlock == nil) return _tail;

        if (self.hasDependency) {
            if (self.dependencyBlock != nil) {
                _dependency = self.dependencyBlock();
                self.dependencyBlock = nil;
            }

            RACSequence * (^tailBlock)(id) = untypedTailBlock;
            _tail = tailBlock(_dependency);
        } else {
            RACSequence * (^tailBlock)(void) = untypedTailBlock;
            _tail = tailBlock();
        }

        if (_tail.name == nil) _tail.name = self.name;

        self.tailBlock = nil;
        return _tail;
    }
}

在調(diào)用tailBlock前也會調(diào)用dependencyBlock辜贵。

我們再回到bind方法

  1. headBlock是入?yún)閕d悯蝉,直接返回passthroughSequence的head,并不使用入?yún)ⅰ?/p>

  2. tailBlock是入?yún)閕d托慨,返回值為RACSequence鼻由。由于RACSequence的定義類似遞歸定義的,所以tailBlock會再次遞歸調(diào)用bind:passingThroughValuesFromSequence:產(chǎn)生一個RACSequence作為新的sequence的tail供下次調(diào)用。

  3. dependencyBlock就是成為了headBlock和tailBlock閉包執(zhí)行之前要執(zhí)行的閉包蕉世。

  4. dependencyBlock的目的是為了把原來的sequence里面的值蔼紧,都進行一次變換。current是入?yún)assthroughSequence狠轻,valuesSeq就是原sequence的引用歉井。每次循環(huán)一次就取出原sequence的頭,直到取不到為止哈误,就是遍歷完成。

  5. 取出valuesSeq的head躏嚎,傳入bindBlock( )閉包進行變換蜜自,返回值是一個current 的sequence。在每次headBlock和tailBlock之前都會調(diào)用這個dependencyBlock卢佣,變換后新的sequence的head就是current的head重荠,新的sequence的tail就是遞歸調(diào)用傳入的current.tail。

上面是當需要使用block的時候再去調(diào)用虚茶,RACSequence還有另一種bind的實現(xiàn)方式戈鲁,那就是她的子類RACEagerSequence

- (instancetype)bind:(RACStreamBindBlock (^)(void))block {
    NSCParameterAssert(block != nil);
    RACStreamBindBlock bindBlock = block();
    NSArray *currentArray = self.array;
    NSMutableArray *resultArray = [NSMutableArray arrayWithCapacity:currentArray.count];
    
    for (id value in currentArray) {
        BOOL stop = NO;
        RACSequence *boundValue = (id)bindBlock(value, &stop);
        if (boundValue == nil) break;

        for (id x in boundValue) {
            [resultArray addObject:x];
        }

        if (stop) break;
    }
    
    return [[self.class sequenceWithArray:resultArray offset:0] setNameWithFormat:@"[%@] -bind:", self.name];
}

可以看出這個地方直接就調(diào)用了bindBlock
通過一個例子說明兩種bind的區(qū)別

- (void)testSequece {
    NSArray *array = @[@1,@2,@3,@4,@5];
    

    RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
        NSLog(@"lazySequence");
        return @(101);
    }];

    
}

這個時候控制臺是沒有打印的嘹叫,因為只是保存了block婆殿;
若想有打印

- (void)testSequece {
    NSArray *array = @[@1,@2,@3,@4,@5];
    

    RACSequence *lazySequence = [array.rac_sequence map:^id(id value) {
        NSLog(@"lazySequence");
        return @(101);
    }];

    [lazySequence array];
}

因為[lazySequence array]方法的內(nèi)部會調(diào)用.head,因為實現(xiàn)了NSFastEnumeration,for循環(huán)實際是自定義的循環(huán)會走到- (NSUInteger)countByEnumeratingWithState: objects:count:方法

也可以這樣

- (void)testSequece {
    NSArray *array = @[@1,@2,@3,@4,@5];
    

    RACSequence *lazySequence = [array.rac_sequence.eagerSequence map:^id(id value) {
        NSLog(@"lazySequence");
        return @(101);
    }];
}

這樣會變成RACEagerSequence對象罩扇,從而達到及時調(diào)用的目的婆芦。

可以通過signal方法將RACSquenceRACSignal關聯(lián)上

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市喂饥,隨后出現(xiàn)的幾起案子消约,更是在濱河造成了極大的恐慌,老刑警劉巖员帮,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件或粮,死亡現(xiàn)場離奇詭異,居然都是意外死亡捞高,警方通過查閱死者的電腦和手機氯材,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來硝岗,“玉大人浓体,你說我怎么就攤上這事”惭龋” “怎么了命浴?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我生闲,道長媳溺,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任碍讯,我火速辦了婚禮悬蔽,結果婚禮上,老公的妹妹穿的比我還像新娘捉兴。我一直安慰自己蝎困,他們只是感情好,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布倍啥。 她就那樣靜靜地躺著禾乘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪虽缕。 梳的紋絲不亂的頭發(fā)上始藕,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機與錄音氮趋,去河邊找鬼伍派。 笑死,一個胖子當著我的面吹牛剩胁,可吹牛的內(nèi)容都是我干的诉植。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昵观,長吁一口氣:“原來是場噩夢啊……” “哼倍踪!你這毒婦竟也來了?” 一聲冷哼從身側響起索昂,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤建车,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后椒惨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體缤至,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年康谆,在試婚紗的時候發(fā)現(xiàn)自己被綠了领斥。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡沃暗,死狀恐怖月洛,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情孽锥,我是刑警寧澤嚼黔,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布细层,位于F島的核電站,受9級特大地震影響唬涧,放射性物質(zhì)發(fā)生泄漏疫赎。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一碎节、第九天 我趴在偏房一處隱蔽的房頂上張望捧搞。 院中可真熱鬧,春花似錦狮荔、人聲如沸胎撇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽晚树。三九已至,卻和暖如春受葛,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背偎谁。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工总滩, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人巡雨。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓闰渔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親铐望。 傳聞我的和親對象是個殘疾皇子冈涧,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內(nèi)容