ReactiveCocoa相关实用轮子ReactiveCocoa

RACSignal、RACSequence、RACTuple

2017-02-17  本文已影响318人  不会飞的小白

(大喇叭:)本篇比较长请耐心阅读,主要解释一下RACSignal的部分内容,详细介绍了RACSequence和RACTuple的原理,介绍了Push-Driven和Pull-Driven。

简介

ReactiveCocoa是由git开源的应用于iOS和OS开发的重型FRP (Functional Reactive Programming 是一种响应变化的编程范式) 框架。内部使用了大量的block。FRP的核心就是信号。

RACStream

This class represents a monad, upon which many stream-based operations can be built.

RACStream是一种流。本身并没有特别大的用处,主要在两个子类RACSignal和RACSequence上得到体现。

由于RACStream是一个根类,所以暂时先不做介绍,它的一些方法主要会用在RACSignal中。先把周边介绍一下在回过头来看,就不会那么害怕了。

RACSignal

RACSignal是ReactiveCocoa中的重要概念,是一种信号流,继承于RACStream。信号就是数据流,可以用来传递和绑定。

这个RACSignal通常是冷信号(关于冷信号与热信号之后章节中再详细说明)。

这里引用一下这里的解释,感觉很形象。

使用ReactiveCocoa实现iOS平台响应式编程

把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能抱枕玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开放默认是关的,除非有了接收方(subscriber),才会重新打开。这样只要有新的玻璃球进来,就会自动传送给接收方。

创建一个信号 +createSignal:

通常情况下,我们使用[RACSignal createSignal:]来创建信号。
信号的发送过程查看之前的文章:ReactiveCocoa信号发送详解

RACSignal会发送三种信号状态,sendNext:sendError:sendCompleted

  1. sendNext:可以发送多个next,subscriber会依次接受发送的数据。
  2. sendError:发送错误,为NSError对象。
  3. sendCompleted:发送成功,整个流程完成,不会再发送新的next。
    在一个Racsignal的生命周期中,可以发送多个next数据,但是只能发送一个Error或者Completed。

+error:

返回一个信号,订阅后,立即发送错误

+never

返回一个永远不会完成的信号。

+startEagerlyWithScheduler:block:

+startLazilyWithScheduler:block:

这个方法跟下面的eagerSequence有点类似。在没有订阅该信号的情况下,会执行一次block中的内容。这个方法通常会用在异步处理网络请求上。详细的原理,在理解RACMulticastConnection之后就会比较清楚了。

RACSignal *signal1 = [[RACSignal startEagerlyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
  NSLog(@"signal 1、、、、、、");
  [subscriber sendNext:@"1"];
  [subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];
   
RACSignal *signal2 = [[RACSignal startLazilyWithScheduler:[RACScheduler schedulerWithPriority:RACSchedulerPriorityBackground] block:^(id<RACSubscriber> subscriber) {
   NSLog(@"signal 2、、、、、、、");
   [subscriber sendNext:@"2"];
   [subscriber sendCompleted];
}] deliverOn:[RACScheduler mainThreadScheduler]];

//运行代码在没有订阅信号的时候会有输出,是因为在[RACMulticastConnection connect]中会有subscribe发生。
//ReactiveCocoaTest[8783:14103027] signal 1、、、、、、

+return:

直接返回一个信号,并发送一个value。

+empty

返回一个nil

-concat:

把一个信号拼接到另一个信号后面,当多个信号发送时,有序的接受信号。如果第一个信号没有发送完成,则后续的信号不会执行。

RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"signalA"];
    [subscriber sendCompleted];
    return nil;
}];
    
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"signalB"];
    return nil;
}];

// 将两个信号拼接
RACSignal *signal = [signalA concat:signalB];
[signal subscribeNext:^(id x) {
    NSLog(@"%@",x);
}];
// 此时会输出signalA、signalB。如果调换位置[signalB concat:signalA],则只会输出signalB

<b>原理:</b>

  1. 创建了一个新的信号。
  2. 这个新的信号内部中,订阅了当前的信号singalA,依次发送数据sendNext:
  3. 当发送完成,执行completed,此时signalB会被订阅者订阅
- (RACSignal *)concat:(RACSignal *)signal {
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        RACSerialDisposable *serialDisposable = [[RACSerialDisposable alloc] init];

        RACDisposable *sourceDisposable = [self subscribeNext:^(id x) {
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            RACDisposable *concattedDisposable = [signal subscribe:subscriber];
            serialDisposable.disposable = concattedDisposable;
        }];

        serialDisposable.disposable = sourceDisposable;
        return serialDisposable;
    }] setNameWithFormat:@"[%@] -concat: %@", self.name, signal];
}

-zipWith:

压缩信号,将两个信号压缩之后,把数据合并成一个RACTuple发送出去。

<b>原理:</b>

- (RACSignal *)zipWith:(RACSignal *)signal {
    NSCParameterAssert(signal != nil);
    // 创建一个新信号
    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        __block BOOL selfCompleted = NO;
        NSMutableArray *selfValues = [NSMutableArray array];

        __block BOOL otherCompleted = NO;
        NSMutableArray *otherValues = [NSMutableArray array];
        // 发送成功
        void (^sendCompletedIfNecessary)(void) = ^{
            @synchronized (selfValues) {
                BOOL selfEmpty = (selfCompleted && selfValues.count == 0);
                BOOL otherEmpty = (otherCompleted && otherValues.count == 0);   
                // 两个信号都发送成功则新信号发送成功
                if (selfEmpty || otherEmpty) [subscriber sendCompleted];
            }
        };

        // sendNext
        void (^sendNext)(void) = ^{
            @synchronized (selfValues) {
                if (selfValues.count == 0) return;
                if (otherValues.count == 0) return;

                RACTuple *tuple = [RACTuple tupleWithObjects:selfValues[0], otherValues[0], nil];
                [selfValues removeObjectAtIndex:0];
                [otherValues removeObjectAtIndex:0];
                // 当压缩成一个RACTuple时,会将第一次发送的数据从数组中删除
                // 发送数据
                [subscriber sendNext:tuple];
                sendCompletedIfNecessary();
            }
        };
        // 订阅singalA被订阅
        RACDisposable *selfDisposable = [self subscribeNext:^(id x) {
            @synchronized (selfValues) {
                [selfValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                selfCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];
        // singalB订阅
        RACDisposable *otherDisposable = [signal subscribeNext:^(id x) {
            @synchronized (selfValues) {
                [otherValues addObject:x ?: RACTupleNil.tupleNil];
                sendNext();
            }
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            @synchronized (selfValues) {
                otherCompleted = YES;
                sendCompletedIfNecessary();
            }
        }];

        return [RACDisposable disposableWithBlock:^{
            [selfDisposable dispose];
            [otherDisposable dispose];
        }];
    }] setNameWithFormat:@"[%@] -zipWith: %@", self.name, signal];
}

  1. 创建一个新信号,singalA和singalB两个信号都会在内部被订阅。
  2. 当singalA发送信号,会判断singalB有没有发送信号,同理,sigbalB发送信号时会检测singalA有没有发送信号,当两个都发送了信号之后会打包成一个RACTuple。
  3. sendCompleted也是同理。

订阅 -subscribe

创建了signal之后,本身是不会做任何处理,必须订阅这个信号,才能为我所用。
通常情况下使用[signal subscribeNext:]直接订阅信号,这个方法等返回值是一个RACDisposable类型的值。也就是我们可以在外部对这个信号进行操作,比如取消订阅[disposable dispose]

// creat a signal
RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
    [subscriber sendNext:@"a"];
    [subscriber sendCompleted];
    // 取消订阅要调用这里block块
    return [RACDisposable disposableWithBlock:^{
        NSLog(@"disposed");
    }];
}] doNext:^(id x) {
    NSLog(@"do next");
}] doCompleted:^{
    NSLog(@"do Completed");
}];

// subscribe do subscription
[signal subscribeNext:^(id x) {
    // subscription
    NSLog(@"subscriber receive value: %@", x);
} completed:^{
    NSLog(@"completed end");
}];

doNext:、doCompleted、doError:

此处只用doNext:来做说明,其它两个与此类似。

doNext:是在执行sendNext:之前执行的操作,就是订阅成功之后,将要发送订阅数据时会先执行此方法。

// RACSignal+Operations.m 源码
- (RACSignal *)doNext:(void (^)(id x))block {
    NSCParameterAssert(block != NULL);

    return [[RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        return [self subscribeNext:^(id x) {
            // 先会掉block
            block(x);
            // 执行订阅发送
            [subscriber sendNext:x];
        } error:^(NSError *error) {
            [subscriber sendError:error];
        } completed:^{
            [subscriber sendCompleted];
        }];
    }] setNameWithFormat:@"[%@] -doNext:", self.name];
}

<b>注意:</b>doNext:方法返回的事一个新的signal,这是一个新创建的信号,并且在[subscriber sendNext:x]之前,首先回调了block(x)。所以,如果将doNext:单独拿出来,在之后的信号订阅过程中,是不会执行doNext:方法。因为订阅的是最原始的信号。

看一下上面代码的打印结果,如下:

RACDemo[44640:4871628] do next
RACDemo[44640:4871628] subscriber receive value a
RACDemo[44640:4871628] do Completed
RACDemo[44640:4871628] completed end

<b>RACSiganle先说这么多,其他的会在下一篇文章中详细解释。</b>

RACSequence

单纯的翻译来看就是<b>序列</b>。那这个是用来干嘛的呢?

Represents an immutable sequence of values. Unless otherwise specified, the sequences' values are evaluated lazily on demand. Like Cocoa collections, sequences cannot contain nil.

官方是这么解释的,通俗的翻译一下就是:
RACSequence代表的是一组不可变的序列值。除非另有说明,这个序列值在使用的时候通过懒加载的方式。跟cocoa框架中的集合一样,不能包含空值。

想到这里,就会想到Foundation中的NSArrayNSDictionary。不错,这里可以将NSArrayNSDictionary转换为RACSequence。并且通过RACSequence转为RACSignal

<b>NSArray转换 --> RACArraySequence</b>

NSArray *array = @[@"a", @"b", @"c"];
// 可以调用array.rac_sequence直接做转换
RACSequence *arraySeq = array.rac_sequence;
RACSignal *arraySignal = arraySeq.signal;
    
[arraySignal subscribeNext:^(id x) {
    NSLog(@"subscriber array receive value: %@",x);
}];

源码

// NSArray+RACSequenceAdditions.m
- (RACSequence *)rac_sequence {
    return [RACArraySequence sequenceWithArray:self offset:0];
}

// RACArraySequence.m
+ (instancetype)sequenceWithArray:(NSArray *)array offset:(NSUInteger)offset {
    NSCParameterAssert(offset <= array.count);

    if (offset == array.count) return self.empty;

    RACArraySequence *seq = [[self alloc] init];
    seq->_backingArray = [array copy];
    seq->_offset = offset;
    return seq;
}

上述源码中提到了RACArraySequence,调用了一个类方法直接返回RACArraySequence
这个RACArraySequence中有两个属性分别存储数组和偏移。这个offset值得一提,代表的是这个Sequence是从array的第几个开始的。接下来就会看到。往下看

// 获取头
- (id)head {
    return self.backingArray[self.offset];
}

// 获取尾部
- (RACSequence *)tail {
    RACSequence *sequence = [self.class sequenceWithArray:self.backingArray offset:self.offset + 1];
    sequence.name = self.name;
    return sequence;
}

// 获取数组
- (NSArray *)array {
    return [self.backingArray subarrayWithRange:NSMakeRange(self.offset, self.backingArray.count - self.offset)];
}

这个head返回的值是数组中第offset个数据。返回的array是从第offset个开始的。

tail这个方法返回的还是一个序列,而且这个序列是从第offset+1个数据开始的,有可能回返回empty。
以上的三个方法会在RACSequence中详细介绍。

- (NSUInteger)countByEnumeratingWithState:(NSFastEnumerationState *)state objects:(id __unsafe_unretained _Nullable [_Nonnull])buffer count:(NSUInteger)len;

这个方法是NSFastEnumeration协议中的方法,不做显式调用。在这里就不去追究了。

<b>NSDictionary转换</b>

NSDictionary *dict = @{@"key1" : @"value1",
                       @"key2" : @"value2"};
RACSequence *dictSeq = dict.rac_sequence;
RACSignal *dictSignal = dictSeq.signal;
    
// 字典中的数据流比较特殊、是一个`RACTuple`类型,下面会讲到。
[dictSignal subscribeNext:^(RACTuple *tupe) {
    NSLog(@"key = %@, value = %@", tupe.first, tupe.second);
    }];
// 所有的key的序列
RACSequence *keysSeq = dict.rac_keySequence;
// 所有的value的序列
RACSequence *valuesSeq = dict.rac_valueSequence;
- (RACSequence *)rac_sequence {
    NSDictionary *immutableDict = [self copy];

    // TODO: First class support for dictionary sequences.
    return [immutableDict.allKeys.rac_sequence map:^(id key) {
        id value = immutableDict[key];
        return RACTuplePack(key, value);
    }];
}

源码中可以看到是通过获取dictionary所有key来重新组成一个新的序列。主要的就是一个map函数,不要着急,马上就会介绍。

RACSequence的方法

这个东西,真的是很牛x的,感慨一下。源文件就不贴了,自己看吧,一个一个介绍。

head、tail、array

// 返回第一个序列中的第一个数据
@property (nonatomic, strong, readonly) id head;

// 返回除了第一个数据之外的其他数据组成的序列
@property (nonatomic, strong, readonly) RACSequence *tail;

// 返回一个数组
@property (nonatomic, copy, readonly) NSArray *array;

这几个属性就不说了,上面已经介绍过了。

objectEnumerator

// RACSequence.h
@property (nonatomic, copy, readonly) NSEnumerator *objectEnumerator;

// RACSequence.m
- (NSEnumerator *)objectEnumerator {
    RACSequenceEnumerator *enumerator = [[RACSequenceEnumerator alloc] init];
    enumerator.sequence = self;
    return enumerator;
}

这个objectEnumerator其实是一个RACSequenceEnumerator,是一个遍历,来自NSEnumerator

-eagerSequence、-lazySequence

/// Converts a sequence into an eager sequence.
///
/// An eager sequence fully evaluates all of its values immediately. Sequences
/// derived from an eager sequence will also be eager.
///
/// Returns a new eager sequence, or the receiver if the sequence is already
/// eager.
@property (nonatomic, copy, readonly) RACSequence *eagerSequence;

/// Converts a sequence into a lazy sequence.
///
/// A lazy sequence evaluates its values on demand, as they are accessed.
/// Sequences derived from a lazy sequence will also be lazy.
///
/// Returns a new lazy sequence, or the receiver if the sequence is already lazy.
@property (nonatomic, copy, readonly) RACSequence *lazySequence;

这里需要详细解释一下。

在一开始提到RACSequence的时候,我们就已经说过:<b>除非另有说明,这个序列值在使用的时候通过懒加载的方式。</b>而eagerSequence则是lazy的对立面。

// 通过RACEagerSequence返回
- (RACSequence *)eagerSequence {
    return [RACEagerSequence sequenceWithArray:self.array offset:0];
}

// 返回自己
- (RACSequence *)lazySequence {
    return self;
}

我们先不看其原理是怎么样的,我们先来看个例子。原理在下一章会讲。

NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
// 这里又用到了map函数(映射),马上就会讲到
RACSequence *seq1 = [array1.rac_sequence map:^id(id value) {
    NSLog(@"lazy value = %@", value);
    return value;
}];
    
RACSequence *seq2 = [array2.rac_sequence.eagerSequence map:^id(id value) {
    NSLog(@"eager value = %@", value);
    return value;
}];
    
NSArray *newArray1 = seq1.array;
NSArray *newArray2 = seq2.array;

// 打印结果
RACDemo[26685:1927997] eager value = 1
RACDemo[26685:1927997] eager value = 2
RACDemo[26685:1927997] eager value = 3
RACDemo[26685:1927997] lazy value = a
RACDemo[26685:1927997] lazy value = b
RACDemo[26685:1927997] lazy value = c

从打印结果来看,先打印的eager,然后才是lazy。在这里即使将后两行代码注释掉,也会执行eager里的NSLog。先剧透一下原理,其实就是bind,大家可以先了解一下。

-signal、-signalWithScheduler:

- (RACSignal *)signal;

- (RACSignal *)signalWithScheduler:(RACScheduler *)scheduler;

RACSequence转为RACSignal

RACScheduler在合适的时候会说一下这个类,内部使用GCD实现的。

-foldLeftWithStart:reduce:、-foldRightWithStart:reduce

- (id)foldLeftWithStart:(id)start reduce:(id (^)(id accumulator, id value))reduce;
- (id)foldRightWithStart:(id)start reduce:(id (^)(id first, RACSequence *rest))reduce;

这两个方法都是遍历、给一个开始的值,一个是从左开始遍历,一个是从又开始遍历。简化方法就是:

[1,2,3] startValue

left: reduce(reduce(reduce(startValue,1),2),3)

right reduce(reduce(reduce(1,startValue),2),3)

举个🌰吧,看的更真切。

NSArray *array1 = @[@"a", @"b", @"c"];
NSArray *array2 = @[@1, @2, @3];
    
id a = [array1.rac_sequence foldLeftWithStart:@"d" reduce:^id(id accumulator, id value) {
    NSLog(@"accumulator = %@, value = %@", accumulator, value);
    return [NSString stringWithFormat:@"%@ + %@",accumulator, value];
}];
NSLog(@"a = %@", a);
    
id b = [array2.rac_sequence foldRightWithStart:@(4) reduce:^id(id first, RACSequence *rest) {
    NSLog(@"first = %@, rest.head = %@",first, rest.head);
    return @([first integerValue] + [rest.head integerValue]);
}];
NSLog(@"b = %@", b);

// 打印结果:
RACDemo[27480:1956629] accumulator = d, value = a
RACDemo[27480:1956629] accumulator = d + a, value = b
RACDemo[27480:1956629] accumulator = d + a + b, value = c
RACDemo[27480:1956629] a = d + a + b + c
RACDemo[27480:1956629] first = 3, rest.head = 4
RACDemo[27480:1956629] first = 2, rest.head = 7
RACDemo[27480:1956629] first = 1, rest.head = 9
RACDemo[27480:1956629] b = 10

-any、-all

- (BOOL)any:(BOOL (^)(id value))block;
- (BOOL)all:(BOOL (^)(id value))block;

这两个都是测试型函数,比较简单。

  1. any是序列中的任一一个元素满足某个条件为true。
  2. all则是序列中的所有元素都满足某一条件返回true。内部调用foldLeftWithStart:一个一个比较。这里感觉不太好,如果有一个不满足就应该直接返回BOOL值,剩下的元素就不应该再比较了。浪费资源啊。

-objectPassingTest:

这个函数是在any:函数中调用的。内部调用了flatmap:。满足条件的记录下来,不满足条件的过滤掉,如果有多个数据满足条件,返回第一个。

- (id)objectPassingTest:(BOOL (^)(id value))block;

+sequenceWithHeadBlock:tailBlock:

+ (RACSequence *)sequenceWithHeadBlock:(id (^)(void))headBlock tailBlock:(RACSequence *(^)(void))tailBlock;

这个就更好理解了,这是一个类方法,通过我们之前了解到的headtail大概也能知道,就是自己定义head和tail组合成一个新的RACSequence

RACTupleNil

RACTuple.h中有一个RACTupleNil类,这个类就是为了表示一个null的元素。也就是说RACTuple的元素中有null则用RACTupleNil表示。

RACTuple中,用RACTupleNil来表示元组中没有内容。

RACTupleNil是一个单例,用RACTupleNil.tupleNil来表示。
不知道设计这个类的目的是什么?

RACTuple

这是一个遵循NSCoding, NSCopying, NSFastEnumeration协议的继承自NSObject的有序的序列。

属性

  1. count tuple中的元素个数
  2. frist tuple中第一个元素,second/third/fourth/fifth
  3. last 最后一个元素
    这几个属性是只读,获取当前tuple中的第几个元素。当index>count时,返回nil。

RACTuple.m中,我们看看RACTuple的具体实现

@interface RACTuple ()
@property (nonatomic, strong) NSArray *backingArray;
@end

这里有一个数组,用来表示元组中的元素。是一个不可变的数组

#pragma mark NSCopying
- (instancetype)copyWithZone:(NSZone *)zone {
    // we're immutable, bitches!
    return self;
}

上面的源代码中有一句作者的注释,说这是一个不可变的,也就是说元组是不可变的。

+tupleWithObjectsFromArray:

将数组转换成tuple,在实现中调用的是下面的方法。

+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array {
    return [self tupleWithObjectsFromArray:array convertNullsToNils:NO];
}

+tupleWithObjectsFromArray:convertNullsToNils:

将数组转换为tuple,如果convertYES则将NSNUll转换为RACTupleNil

RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]]];
NSLog(@"%@", tuple);
RACTuple *tuple1 = [RACTuple tupleWithObjectsFromArray:@[@(1), @(2), [NSNull null]] convertNullsToNils:YES];
NSLog(@"%@", tuple1);

在这里我们发现这两个输出显示的元组中的元素是一样的。
ReactiveCocoaTest[40628:6604925] <RACTuple: 0x600000005ea0> (
    1,
    2,
    "<null>"
)
ReactiveCocoaTest[40628:6604925] <RACTuple: 0x608000005c00> (
    1,
    2,
    "<null>"
)

那这两个到底有什么区别?

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

从上面的源码中可以看到,当convert == YES时,会将NSNull.null转为RACTupleNil.tupleNil。存储的数据发生了变化。在控制台输出时,会调用[class description]方法。

- (NSString *)description {
    return [NSString stringWithFormat:@"<%@: %p> %@", self.class, self, self.allObjects];
    //这里打印的是self.allObjects
}

allObjects是一个数组,在其内部又会将RACTupleNil.tupleNil转换为NSNull.null,所以控制台的输出结果是一样的。

- (NSArray *)allObjects {
    NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:self.backingArray.count];
    for (id object in self.backingArray) {
        [newArray addObject:(object == RACTupleNil.tupleNil ? NSNull.null : object)];
    }
    
    return newArray;
}

-tupleWithObjects:

类似于数组的初始化方法,将数据转为tuple

-objectAtIndex:

获取当前index的元素,如果index > count则返回nil。

-allObjects

获取tuple中的所有元素,组成一个数组。

-tupleByAddingObject:

类似于可变数组,将一个元素拼接在tuple的元素后面并返回一个新的tuple。

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

这个方法传进来两个值,一个是tuple,一个是array(注意:数组中的元素是NSValue)
遍历数组,取数组的每一个元素的指针,然后将tuple对应的值赋值给这个指针对应的值。

NSString *str1 = @"1";
NSString *str2 = @"2";
NSString *str3 = @"3";
NSArray *array = @[[NSValue valueWithPointer:&str1], 
                   [NSValue valueWithPointer:&str2],
                   [NSValue valueWithPointer:&str3]];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);
    
RACTupleUnpackingTrampoline *tupleTramp = RACTupleUnpackingTrampoline.trampoline;
RACTuple *tuple = RACTuplePack(@"a", @"b", @"c");
[tupleTramp setObject:tuple forKeyedSubscript:array];
NSLog(@"str1 = %@, str2 = %@, str3 = %@", str1, str2, str3);

看看输出的结果:

ReactiveCocoaTest[78079:10162531] str1 = 3, str2 = 4
ReactiveCocoaTest[78079:10162531] st1 = 3, st2 = 4

其实这样的方法是没有什么实际用处的,也不会有人这么去写。但是定义了这个类,就肯定有它的实际意义。接下来我们看看RACTuple的两个宏定义。

RACTuplePack、RACTupleUnpack

  1. RACTuplePack
    将数据包装成一个RACTuple类型。至少有一个数据。
/// Creates a new tuple with the given values. At least one value must be given.
/// Values can be nil.
#define RACTuplePack(...) \
    RACTuplePack_(__VA_ARGS__)

#define RACTuplePack_(...) \
    ([RACTuple tupleWithObjectsFromArray:@[ metamacro_foreach(RACTuplePack_object_or_ractuplenil,, __VA_ARGS__) ]])
    
#define RACTuplePack_object_or_ractuplenil(INDEX, ARG) \
    (ARG) ?: RACTupleNil.tupleNil,

首先这个宏定义中使用了[RACTuple tupleWithObjectsFromArray:]这个函数来创建一个新的tuple。

RACTuple *tuple = RACTuplePack(@1, @2);
  1. RACTupleUnpack
///   RACTupleUnpack(NSString *string, NSNumber *num) = [RACTuple tupleWithObjects:@"foo", @5, nil];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
///
///   /* The above is equivalent to: */
///   RACTuple *t = [RACTuple tupleWithObjects:@"foo", @5, nil];
///   NSString *string = t[0];
///   NSNumber *num = t[1];
///   NSLog(@"string: %@", string);
///   NSLog(@"num: %@", num);
#define RACTupleUnpack(...) \
        RACTupleUnpack_(__VA_ARGS__)

在源文件中已经给出了例子了,就不在举例子了。我们仔细看看这个宏是怎么搞的

#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(NSString *st1, NSString *st2) = [RACTuple tupleWithObjects:@"3", @"4", nil];

这个代码的形式跟Swift的元组很像。

let (intA, intB) = (1, 2)

接下来,我们对这段代码进行编译:

__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) id RACTupleUnpack79_var1;
int RACTupleUnpack_state79 = 0; RACTupleUnpack_after79: ;
__attribute__((objc_ownership(strong))) NSString *st1 = RACTupleUnpack79_var0;
__attribute__((objc_ownership(strong))) NSString *st2 = RACTupleUnpack79_var1;
    
if (RACTupleUnpack_state79 != 0)
    RACTupleUnpack_state79 = 2;
while (RACTupleUnpack_state79 != 2)
    if (RACTupleUnpack_state79 == 1) {
        goto RACTupleUnpack_after79;
    }
    else
        for (; RACTupleUnpack_state79 != 1; RACTupleUnpack_state79 = 1) 
            [RACTupleUnpackingTrampoline trampoline][ @[ [NSValue valueWithPointer:&RACTupleUnpack79_var0], [NSValue valueWithPointer:&RACTupleUnpack79_var1], ] ] = [RACTuple tupleWithObjects:@"3", @"4", ((void *)0)];

看到这一坨代码,到最后是不是感觉瞬间懂了。
通过[NSValue valueWithPointer:]获取指针放在数组中。然后通过赋值的方式将数据分别传给两个指针。

总结:聊一聊Push-Driven、Pull-Driven

RACStream.png

RACSignal和RACSequence都是继承自RACStream。信号是Push-Driven的信号流,序列是Pull-Driven的序列流。

Push-driven means that values for the signal are not defined at the moment of signal creation and may become available at a later time (for example, as a result from network request, or any user input).

Push-driven : 在创建信号的时候,信号不会被立即赋值,之后才会被赋值(举个栗子:网络请求回来的结果或者是任意的用户输入的结果)。

Pull-driven means that values in the sequence are defined at the moment of signal creation and we can query values from the stream one-by-one.

Pull-driven : 在创建信号的同时序列中的值就会被确定下来,我们可以从流中一个个地查询值。

RACSequence <=> RACSignal

RACSequence也可以转换为RACSignal,转换时需要把sequence里面的值放到一个RACScgeduler中,按照顺序把值全部push到新创建的RACSignal中。

RACSignal也可以转换为RACSequence,转换时需要收集所有发送到这个signal的值,直到complete完成。会用到[signal toArray]这个方法,这个方法会阻塞当前线程。

RACSequence:是Pull-Driven。除了特别声明(eager)都是懒加载。在创建时,序列中的值就已经确定。当被订阅时会直接返还给订阅者。当转换为signal后,会将数据按照顺序全部push到信号。

RACSignal:是Push-Driven。signal是有订阅者的存在才有意义(一般的信号都是冷信号,被创建之后,只有订阅才会被激活)。signal发送的不仅仅是数据,还包括状态(error,complete)。

下一节来重点说说bind:等。

参考链接:

iOS的函数响应型编程

ReactiveCocoa模式--Signal

Reactive Cocoa Tutorial [3] = RACSignal的巧克力工厂

谢谢大家,有写的不对的地方,欢迎指正,共同进步。

上一篇下一篇

猜你喜欢

热点阅读