ReactiveCocoa 中 集合類 RACTuple/RACSequence

ReactiveCocoa 中 集合類 RACTuple/RACSequence

本篇目錄
1. 解釋一下`RACTuple`中的一些難點(diǎn);
2. RACSequence底層實(shí)現(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其實(shí)是同一個(gè)方法,實(shí)現(xiàn)很簡(jiǎn)單,其實(shí)就是初始化一個(gè)RACTuple類型的對(duì)象,至于為什么使用[[self alloc] init],而不使用[[RACTuple alloc] init]是為了當(dāng)RACTuple子類調(diào)用的時(shí)候可以直接初始化子類,根據(jù)convert入?yún)⒌牟煌瑏?lái)判斷對(duì)數(shù)組的內(nèi)部是否的Null對(duì)象替換成RACTupleNil.tupleNil,很簡(jiǎn)單不對(duì)說(shuō)了;
  2. 我們看下方法3,
  1. va_list用于聲明一個(gè)變量,我們知道函數(shù)的可變參數(shù)列表其實(shí)就是一個(gè)字符串,所以va_list才被聲明為字符型指針,這個(gè)類型用于聲明一個(gè)指向參數(shù)列表的字符型指針變量,例如:va_list ap;//ap:arguement pointer
  2. va_start(ap,v),它的第一個(gè)參數(shù)是指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)函數(shù)的第一個(gè)參數(shù),通常用于指定可變參數(shù)列表中參數(shù)的個(gè)數(shù)。
  3. va_arg(ap,t),它的第一個(gè)參數(shù)指向可變參數(shù)字符串的變量,第二個(gè)參數(shù)是可變參數(shù)的類型。
  4. va_end(ap) 用于將存放可變參數(shù)字符串的變量清空(賦值為NULL)。

由上面4個(gè)引用應(yīng)該沒(méi)什么問(wèn)題了。

  1. 通過(guò)下標(biāo)取出元素

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

```
- (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];
}
```
這是怎么做到的呢,關(guān)鍵在于下面的代碼

```
@implementation RACTuple (ObjectSubscripting)

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

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

代碼追蹤可以知道RACTuplePack這個(gè)宏其實(shí)就是

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

```
這個(gè)倒沒(méi)什么好說(shuō)的,上面已經(jīng)解釋過(guò)了,就是便利初始化方法

RACTupleUnpack

通過(guò)一個(gè)demo來(lái)說(shuō)明這個(gè)宏到底是干什么的

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

通過(guò)下面的方式查看編譯時(shí)的代碼

也就是下面這個(gè)

    __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, ]]);

去掉一些干擾項(xiàng)其實(shí)就是

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

相當(dāng)于這個(gè)

dic[@"key"] = value

對(duì)應(yīng)上面

  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對(duì)象才會(huì)有這種附值得嗎,為什么RACTupleUnpackingTrampoline也可以呢
首先來(lái)看一下下面的demo,

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

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

我們都知道如果這樣寫(xiě)的話,當(dāng)點(diǎn)擊view的時(shí)候就會(huì)產(chǎn)生crash,控制臺(tái)會(huì)輸出


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

相信這種錯(cuò)誤有一定開(kāi)發(fā)經(jīng)驗(yàn)的人并不陌生

setObject:forKeyedSubscript:

我們?cè)賮?lái)看看RACTupleUnpackingTrampoline的實(shí)現(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];
    }];
}

好吧它重寫(xiě)了這個(gè)方法

4 實(shí)現(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];
    }
    

關(guān)于這個(gè)協(xié)議的上篇已經(jīng)有了詳細(xì)的解釋

二. RACSequence底層實(shí)現(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個(gè)協(xié)議;

  2. 既然是一個(gè)集合必定會(huì)有元素,head表示的是第一個(gè)元素;

  3. tail表示的是尾部,不過(guò)返回值還是RACSequence有點(diǎn)鏈?zhǔn)降囊馑迹咸茁妨?,通過(guò)一直返回當(dāng)前對(duì)象達(dá)到鏈?zhǔn)秸{(diào)用的目的;

  4. array返回RACSequence對(duì)象里面裝著的所有對(duì)象;

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

}

```
之所以能夠for...in...是因?yàn)閷?shí)現(xiàn)了`NSFastEnumeration`協(xié)議,詳細(xì)請(qǐng)看[這篇](http://www.lxweimin.com/p/7a2ffb5e171c)
  1. objectEnumerator通過(guò)她可以遍歷所有的對(duì)象

    - (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這個(gè)后面單獨(dú)講。

(2) 再看初始化方法

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

是不是想起了RACSignal的初始化?有點(diǎn)差不多的意思實(shí)際初始化的是RACSequence的子類RACDynamicSequence,實(shí)際是保存了2個(gè)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;
}

細(xì)心的同學(xué)肯定發(fā)現(xiàn)了還有一個(gè)方法

+ (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;
}

這個(gè)方法是有在bind的時(shí)候才會(huì)用到,這兩個(gè)方法的不同之處就是多了個(gè)dependencyBlock,我們看看RACSequence對(duì)bind是如何實(shí)現(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;
//如果當(dāng)前對(duì)象的head為空表示沒(méi)有數(shù)據(jù)了返回
            if (value == nil) {

                stop = YES;
                return nil;
            }
//執(zhí)行bindBlock
            current = (id)bindBlock(value, &stop);
            if (current == nil) {
                stop = YES;
                return nil;
            }
//取出下一個(gè)對(duì)象賦值給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,并沒(méi)有調(diào)用block,那么哪里會(huì)調(diào)用到這個(gè)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的時(shí)候,就會(huì)調(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前也會(huì)調(diào)用dependencyBlock。

我們?cè)倩氐?code>bind方法

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

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

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

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

  5. 取出valuesSeq的head,傳入bindBlock( )閉包進(jìn)行變換,返回值是一個(gè)current 的sequence。在每次headBlock和tailBlock之前都會(huì)調(diào)用這個(gè)dependencyBlock,變換后新的sequence的head就是current的head,新的sequence的tail就是遞歸調(diào)用傳入的current.tail。

上面是當(dāng)需要使用block的時(shí)候再去調(diào)用,RACSequence還有另一種bind的實(shí)現(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];
}

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

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

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

    
}

這個(gè)時(shí)候控制臺(tái)是沒(méi)有打印的,因?yàn)橹皇潜4媪薭lock;
若想有打印

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

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

    [lazySequence array];
}

因?yàn)?code>[lazySequence array]方法的內(nèi)部會(huì)調(diào)用.head,因?yàn)閷?shí)現(xiàn)了NSFastEnumeration,for循環(huán)實(shí)際是自定義的循環(huán)會(huì)走到- (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);
    }];
}

這樣會(huì)變成RACEagerSequence對(duì)象,從而達(dá)到及時(shí)調(diào)用的目的。

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

  • 前言 在OOP的世界里使用FRP的思想來(lái)編程,光有函數(shù)這種一等公民,還是無(wú)法滿足我們一些需求的。因此還是需要引用變...
    一縷殤流化隱半邊冰霜閱讀 4,045評(píng)論 4 34
  • 1.ReactiveCocoa簡(jiǎn)介 ReactiveCocoa(簡(jiǎn)稱為RAC),是由Github開(kāi)源的一個(gè)應(yīng)用于i...
    清蘂翅膀的技術(shù)閱讀 2,012評(píng)論 0 1
  • 一 導(dǎo)入ReactiveCocoa框架 通常都會(huì)使用CocoaPods(用于管理第三方框架的插件)幫助我們導(dǎo)入po...
    CharType閱讀 830評(píng)論 0 1
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,933評(píng)論 18 139
  • 第一步/獲取瀏覽器內(nèi)置的ajax請(qǐng)求對(duì)象 第二步/ 使用xhr對(duì)象1、使用這個(gè)XHR對(duì)象作同步的get請(qǐng)求 首先用...
    LH_0811閱讀 393評(píng)論 0 0