ReactiveCocoa 中 集合类 RACTuple/RACSequence

ReactiveCocoa 中 集合类 RACTuple/RACSequence

本篇目录
1. 解释一下`RACTuple`中的一些难点;
2. RACSequence底层实现分析。

一. 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其实是同一个方法,实现很简单,其实就是初始化一个RACTuple类型的对象,至于为什么使用[[self alloc] init],而不使用[[RACTuple alloc] init]是为了当RACTuple子类调用的时候可以直接初始化子类,根据convert入参的不同,来判断对数组的内部是否的Null对象替换成RACTupleNil.tupleNil,很简单不对说了;
  2. 我们看下方法3,
  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)。

由上面4个引用应该没什么问题了。

  1. 通过下标取出元素

通常OC的集合类比如NSArrayNSDictionary等等都可以直接通过下标来取出值,比如我们初始化一个NSArray *testArray = @[@"1",@"2",@"3"]; 然后我们直接testArray[0]就可以拿到@"1";我们发现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`类在分类中实现了`objectAtIndexedSubscript`这个方法。
  1. 神奇的RACTupleUnpackRACTuplePack

代码追踪可以知道RACTuplePack这个宏其实就是

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

```
这个倒没什么好说的,上面已经解释过了,就是便利初始化方法

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的时候就会产生crash,控制台会输出


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

相信这种错误有一定开发经验的人并不陌生

setObject:forKeyedSubscript:

我们再来看看RACTupleUnpackingTrampoline的实现

@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 实现了NSFastEnumeration协议

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

关于这个协议的上篇已经有了详细的解释

二. RACSequence底层实现分析。

(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个协议;

  2. 既然是一个集合必定会有元素,head表示的是第一个元素;

  3. tail表示的是尾部,不过返回值还是RACSequence有点链式的意思,老套路了,通过一直返回当前对象达到链式调用的目的;

  4. array返回RACSequence对象里面装着的所有对象;

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

}

```
之所以能够for...in...是因为实现了`NSFastEnumeration`协议,详细请看[这篇](http://www.jianshu.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;
}

细心的同学肯定发现了还有一个方法

+ (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是如何实现了

- (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为空表示没有数据了返回
            if (value == nil) {

                stop = YES;
                return nil;
            }
//执行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,并没有调用block,那么哪里会调用到这个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的时候,就会调用headBlock( ),并且把返回值作为入参;

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

在调用tailBlock前也会调用dependencyBlock。

我们再回到bind方法

  1. headBlock是入参为id,直接返回passthroughSequence的head,并不使用入参。

  2. tailBlock是入参为id,返回值为RACSequence。由于RACSequence的定义类似递归定义的,所以tailBlock会再次递归调用bind:passingThroughValuesFromSequence:产生一个RACSequence作为新的sequence的tail供下次调用。

  3. dependencyBlock就是成为了headBlock和tailBlock闭包执行之前要执行的闭包。

  4. dependencyBlock的目的是为了把原来的sequence里面的值,都进行一次变换。current是入参passthroughSequence,valuesSeq就是原sequence的引用。每次循环一次就取出原sequence的头,直到取不到为止,就是遍历完成。

  5. 取出valuesSeq的head,传入bindBlock( )闭包进行变换,返回值是一个current 的sequence。在每次headBlock和tailBlock之前都会调用这个dependencyBlock,变换后新的sequence的head就是current的head,新的sequence的tail就是递归调用传入的current.tail。

上面是当需要使用block的时候再去调用,RACSequence还有另一种bind的实现方式,那就是她的子类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];
}

可以看出这个地方直接就调用了bindBlock
通过一个例子说明两种bind的区别

- (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]方法的内部会调用.head,因为实现了NSFastEnumeration,for循环实际是自定义的循环会走到- (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对象,从而达到及时调用的目的。

可以通过signal方法将RACSquenceRACSignal关联上

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,298评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,701评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 107,078评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,687评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,018评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,410评论 1 211
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,729评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,412评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,124评论 1 239
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,379评论 2 242
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,903评论 1 257
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,268评论 2 251
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,894评论 3 233
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,014评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,770评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,435评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,312评论 2 260

推荐阅读更多精彩内容