ReactiveCocoa的一些特性细节

本文基于objc版本的ReactiveCocoa v2.5
ps:看这篇文章之前强烈推荐大家用5分钟时间看一下我的上一篇文章毫无干货的带你理解什么是函数式编程


RACSequence

  • 有懒加载和缓存结果的功能,只会把f作用到value上一次。
  • 结果会在第一次使用到的时候再执行block进行计算并缓存,再次使用时从缓存中取出,未用到的结果则不会计算。
- (void)sequence{
    NSArray *strings = @[ @"A", @"B", @"C" ];
    RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) {
        NSLog(@"%@", str);
        return [str stringByAppendingString:@"_"];
    }];
    
    NSLog(@"1");
    NSString *concatA = sequence.head;//打印A
    NSLog(@"2");
    NSString *concatB = sequence.tail.head;//打印B
    NSLog(@"3");
    NSString *concatB2 = sequence.tail.head;//不打印,直接从缓存中取
    NSLog(@"4");
    RACSequence *derivedSequence = [sequence map:^(NSString *str) {
        NSLog(@"5");
        return [@"_" stringByAppendingString:str];
    }];
    NSLog(@"6");
    //tail.head的目的是访问B,但是A和B都已经缓存过了,所以仍然不会重复执行sequence的block。而是打印两次5
    NSString *concatB3 = derivedSequence.tail.head;
    NSLog(@"7");
}

输出结果:1、A、2、B、3、4、6、5、5、7


RACEagerSequence

  • 不进行懒加载,立即将f作用到value上。
  • 需要导入私有类<ReactiveCocoa/RACEagerSequence.h>
- (void)eagerSequence{
    NSArray *strings = @[ @"A", @"B", @"C" ];
    RACSequence *sequence = [strings.rac_sequence map:^(NSString *str) {
        NSLog(@"%@", str);
        return [str stringByAppendingString:@"_"];
    }];
    
    RACEagerSequence *eagerSequence = [sequence eagerSequence];
    NSLog(eagerSequence.head);
    NSLog(eagerSequence.tail.head);
    NSLog(eagerSequence.head);
    NSLog(eagerSequence.tail.head);
} 

执行到[sequence eagerSequence];
输出:A、B、C。
之后输出:A_、B_、A_、B_。


RACSignal

  • signal没有RACSequence那样的懒加载机制。
  • 每一次事件之间只会串行执行,绝不会同事并发多个事件。
  • 所以-subscribeNext:error:completed:内部无需做同步操作
  • subscription始终会在一个RACScheduler进行回调。
  • error具有异常的语义和try catch不同,他会立即将错误发向所有依赖它的signal并且将整个函数链终止。

subscribeNext

  • 每增加一个subscription,函数都会执行一次,会反复产生副作用。
  • 如果signal的副作用出了问题则很难控制和调试,使用时需要谨慎。
    __block int aNumber = 0;
    
    // 每增加一个subscription。createSignal的block机会被执行一次。
    RACSignal *aSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        aNumber++;
        NSLog(@"block执行了%d次",aNumber);
        [subscriber sendNext:@(aNumber)];
        [subscriber sendCompleted];
        return nil;
    }];
    
    [aSignal subscribeNext:^(id x) {
        NSLog(@"subscriber one: %@", x);
    }];
    
    [aSignal subscribeNext:^(id x) {
        NSLog(@"subscriber two: %@", x);
    }];

输出:

2018-11-08 16:51:02.311068+0800 RACDemo[5714:1320947] block执行了1次
2018-11-08 16:51:02.311278+0800 RACDemo[5714:1320947] subscriber one: 1
2018-11-08 16:51:02.311440+0800 RACDemo[5714:1320947] block执行了2次
2018-11-08 16:51:02.311538+0800 RACDemo[5714:1320947] subscriber two: 2

rac_sequence.signal

这种情况并不常用,不过我们还是要分析一下。

RACSignal *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence.signal;
NSLog(@"xxx");
[letters subscribeNext:^(NSString *x) {
    NSLog(@"1%@", x);
}];
NSLog(@"yyy");
[letters subscribeNext:^(NSString *x) {
    NSLog(@"2%@", x);
}];
NSLog(@"zzz");

输出:

2018-11-08 17:55:46.474730+0800 RACDemo[6136:1391178] xxx
2018-11-08 17:55:46.475301+0800 RACDemo[6136:1391178] yyy
2018-11-08 17:55:46.475459+0800 RACDemo[6136:1391178] zzz
2018-11-08 17:55:46.475561+0800 RACDemo[6136:1391225] 1A
2018-11-08 17:55:46.475887+0800 RACDemo[6136:1391225] 2A
2018-11-08 17:55:46.476103+0800 RACDemo[6136:1391225] 1B
2018-11-08 17:55:46.476308+0800 RACDemo[6136:1391225] 2B
2018-11-08 17:55:46.476468+0800 RACDemo[6136:1391225] 1C
2018-11-08 17:55:46.476763+0800 RACDemo[6136:1391225] 2C

当输出到zzz的时候,letters已经添加了两个subscriber。为什么第一次添加subscribeNext的时候不立即输出,而是等到所有subscribeNext添加结束后才输出呢?后面再开个源码分析的文章来单独说明。


RACMulticastConnection

  • 它可以避免像上面那样每添加一个subscribeNext就执行一次block
  • 调用connect后会立即将f作用到value上(block会立即执行),后面再添加任何数量的subscribeNext则不会执行block了,副作用只执行1次。
__block NSInteger index = 0;
    RACSignal *networkRequest = [RACSignal createSignal:^(id<RACSubscriber> subscriber) {
        NSLog(@"inner signal %ld", ++index);
        [subscriber sendNext:@(index)];
        [subscriber sendCompleted];
        return [RACDisposable disposableWithBlock:^{
            
        }];
    }];
    
    RACMulticastConnection *connection = [networkRequest multicast:[RACReplaySubject subject]];
    /*!调用connect后signal中的block会立即执行,后面再调用subscribeNext则不会执行block了,副作用h只执行1次*/
    [connection connect];
    
    [connection.signal subscribeNext:^(NSNumber *index) {
        NSLog(@"subscriber one: %ld", index.integerValue);
    }];

    [connection.signal subscribeNext:^(NSNumber *index) {
        NSLog(@"subscriber two: %ld", index.integerValue);
    }];

输出:

2018-11-08 17:05:54.997850+0800 RACDemo[5852:1349307] inner signal 1
2018-11-08 17:05:54.998385+0800 RACDemo[5852:1349307] subscriber one: 1
2018-11-08 17:05:54.998526+0800 RACDemo[5852:1349307] subscriber two: 1

doNext & doCompleted

  • 设置doNextdoCompleted回调函数后会得到一个新的signal后续需要使用新signal
  • doNext只是设置回调函数,subscribeNext才会触发signal执行block
  • signal中的[subscriber sendNext:@(subscriptions)];会先触发doNext回调,然后再执行subscribeNext
__block unsigned subscriptions = 0;
    NSLog(@"1");
    RACSignal *loggingSignal = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
        NSLog(@"enter signal block");
        subscriptions++;
        //会触发doNext
        [subscriber sendNext:@(subscriptions)];
        //会触发doCompleted
        [subscriber sendCompleted];
        return nil;
    }];
    
    /*!会返回一个新信号
     */
    NSLog(@"2");
    loggingSignal = [loggingSignal doCompleted:^{
        NSLog(@"doCompleted %u", subscriptions);
    }];
    NSLog(@"3");
    loggingSignal = [loggingSignal doNext:^(NSNumber *x) {
        NSLog(@"doNext %ld",x.integerValue);
    }];
    NSLog(@"4");
    //会立即执行
    [loggingSignal subscribeNext:^(NSNumber *x) {
        NSLog(@"subscribeNext %ld",x.integerValue);
    }];
    NSLog(@"5");
    //会立即执行
    [loggingSignal subscribeCompleted:^{
        NSLog(@"subscribeCompleted %u", subscriptions);
    }];
    NSLog(@"6");

输出:

2018-11-10 12:03:08.946714+0800 RACDemo[1403:66623] 1
2018-11-10 12:03:08.949269+0800 RACDemo[1403:66623] 2
2018-11-10 12:03:08.949631+0800 RACDemo[1403:66623] 3
2018-11-10 12:03:08.949755+0800 RACDemo[1403:66623] 4
2018-11-10 12:03:08.950598+0800 RACDemo[1403:66623] enter signal block
2018-11-10 12:03:08.950693+0800 RACDemo[1403:66623] doNext 1
2018-11-10 12:03:08.950799+0800 RACDemo[1403:66623] subscribeNext 1
2018-11-10 12:03:08.950916+0800 RACDemo[1403:66623] doCompleted 1
2018-11-10 12:03:08.951015+0800 RACDemo[1403:66623] 5
2018-11-10 12:03:08.951110+0800 RACDemo[1403:66623] enter signal block
2018-11-10 12:03:08.951177+0800 RACDemo[1403:66623] doNext 2
2018-11-10 12:03:08.951253+0800 RACDemo[1403:66623] doCompleted 2
2018-11-10 12:03:08.951330+0800 RACDemo[1403:66623] subscribeCompleted 2
2018-11-10 12:03:08.951418+0800 RACDemo[1403:66623] 6

Transforming streams

map

  • map的用法是转换stream中的值,然后返回一个新的stream
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *mapped = [letters map:^(NSString *value) {
    return [value stringByAppendingString:value];
}];
[mapped.signal subscribeNext:^(id x) {
    NSLog(x);
}];

输出:AABBCC


filter

  • filter方法会遍历stream把返回YES的元素组成一个新的stream
RACSequence *numbers = [@"1 2 3 4 5 6" componentsSeparatedByString:@" "].rac_sequence;
//只要偶数
RACSequence *filtered = [numbers filter:^ BOOL (NSString *value) {
    return (value.intValue % 2) == 0;
}];
[filtered.signal subscribeNext:^(id x) {
    NSLog(x);
}];

输出:246


concat

  • contact方法会拼接两个stream
  • 对两个RACSequence调用concat效果如同对NSMutableArray使用addObjectsFromArray:
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *concatenated = [letters concat:numbers];
[concatenated.signal subscribeNext:^(id x) {
    NSLog(x);
}];

输出:ABC123


flatten

flatten for sequences

  • 适用于signal of signals类型。
  • 可以将二维的stream拍扁变成一维的strean
  • 但是如果用在多维的signal of signals of signals则会出错误。
RACSequence *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *sequenceOfSequences = @[ letters,numbers ].rac_sequence;
RACSequence *flattened = [sequenceOfSequences flatten];
[flattened.signal subscribeNext:^(id x) {
     NSLog(x);
}];

输出:ABC123

flatten for Signal

  • 实际作用是merge将多个signal合并成一个。
  • 合并后任何一个signal发送数据,都会触发signalblock执行。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *signalOfSignals = [RACSignal createSignal:^ RACDisposable * (id<RACSubscriber> subscriber) {
    [subscriber sendNext:letters];
    [subscriber sendNext:numbers];
    [subscriber sendCompleted];
    return nil;
}];
RACSignal *flattened = [signalOfSignals flatten];
flattened = [flattened doNext:^(NSString *x) {
    NSLog(@"doNext %@", x);
}];
[flattened subscribeNext:^(NSString *x) {
    NSLog(@"subscribeNext %@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

输出:

2018-11-10 14:42:04.989738+0800 RACDemo[2038:140686] doNext A
2018-11-10 14:42:04.989942+0800 RACDemo[2038:140686] subscribeNext A
2018-11-10 14:42:04.990053+0800 RACDemo[2038:140686] doNext 1
2018-11-10 14:42:04.990127+0800 RACDemo[2038:140686] subscribeNext 1
2018-11-10 14:42:04.990221+0800 RACDemo[2038:140686] doNext B
2018-11-10 14:42:04.990315+0800 RACDemo[2038:140686] subscribeNext B
2018-11-10 14:42:04.990440+0800 RACDemo[2038:140686] doNext C
2018-11-10 14:42:04.990535+0800 RACDemo[2038:140686] subscribeNext C
2018-11-10 14:42:04.990634+0800 RACDemo[2038:140686] doNext 2
2018-11-10 14:42:04.990715+0800 RACDemo[2038:140686] subscribeNext 2

flattenMap

flattenMap for sequence

  • 相当于先mapflatten

ps:先后顺序,先map可以修改stream流中的值,然后flatten可以保证生成一个一维的新stream

RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *extended = [[numbers
                              map:^id(id value) {
                                  return @[value,value].rac_sequence;
                              }] flatten];
[extended.signal subscribeNext:^(id x) {
    NSLog(x);
}];

上下两份代码等价。

RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *extended = [numbers flattenMap:^(NSString *num) {//这段代码等同于上面先map再flatten
        return @[ num, num ].rac_sequence;
}];
[extended.signal subscribeNext:^(id x) {
    NSLog(x);
}];

输出:112233
ps:这里不是输出112233这里没有字符串拼接,而是被拍扁了。

  • 在对RACSequence使用flattenMap返回empty并不会存入sequence中,而是会被忽略掉。
RACSequence *numbers = [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence;
RACSequence *edited = [numbers flattenMap:^(NSString *num) {
    if (num.intValue % 2 == 0) {
        //这里必须有返回值但是又不能返回nil,empty不会成为新RACSequence中的项。
        return [RACSequence empty];
    } else {
        NSString *newNum = [num stringByAppendingString:@"_"];
        return [RACSequence return:newNum];
    }
}];
[edited.signal subscribeNext:^(id x) {
    NSLog(x);
}];

输出:1_3_

flattenMap for signal

  • flattenMap是根据前一个信号的参数创建一个新的信号,然后进行一次flatten进行拍扁。
/*!调用登录函数,成功后发送sendNext
 */
- (RACSignal *)signInSignal {
    return [RACSignal createSignal:^RACDisposable *(id subscriber){
        [self.signInService
            signInWithUsername:self.usernameTextField.text
            password:self.passwordTextField.text
            complete:^(BOOL success){
                [subscriber sendNext:@(success)];
                [subscriber sendCompleted];
            }];
            return nil;
    }];
}

/*!点击按钮后调用登录
 */
- (void)flattenMapForSignal{
    [[[self.signInButton
        rac_signalForControlEvents:UIControlEventTouchUpInside]
        flattenMap:^id(id x){
            return [self signInSignal];
        }]
        subscribeNext:^(id x){
            NSLog(@"登录结果: %@", x);
        }];
}

分析:
上例中当按钮点击后流中传递的是按钮点击的信号,通过flattenMap进行信号转换。

  • map按钮点击的信号转换成signInSignal的登录信号。
  • 此后流中传递的就是登录的信号了。
  • 所以我们后来subscribeNext接收到的将会是登录的结果。

Combining signals

then

  • 开始原始信号等到它执行结束之后使用新的值生成一个新的信号
  • 常用的方法是在一个信号里执行所有的副作用,然后返回一个新的信号。
RACSignal *letters = [@"A B C" componentsSeparatedByString:@" "].rac_sequence.signal;
RACSignal *sequenced = [[letters
                             doNext:^(NSString *letter) {
                                 NSLog(@"doNext %@", letter);
                             }]
                            then:^{
                                NSLog(@"then");
                                return [@"1 2 3" componentsSeparatedByString:@" "].rac_sequence.signal;
                            }];
[sequenced subscribeNext:^(id x) {
    NSLog(@"subscribeNext %@",x);
}];

输出:

2018-11-10 15:24:03.646833+0800 RACDemo[2526:204404] doNext A
2018-11-10 15:24:03.647147+0800 RACDemo[2526:204404] doNext B
2018-11-10 15:24:03.647345+0800 RACDemo[2526:204404] doNext C
2018-11-10 15:24:03.647595+0800 RACDemo[2526:204404] then
2018-11-10 15:24:03.647890+0800 RACDemo[2526:204406] subscribeNext 1
2018-11-10 15:24:03.648029+0800 RACDemo[2526:204406] subscribeNext 2
2018-11-10 15:24:03.648158+0800 RACDemo[2526:204406] subscribeNext 3

merge

  • 将多个信号合并成一个信号,这些信号中任何一个收到值都会触发新信号。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *merged = [RACSignal merge:@[ letters, numbers ]];
[merged subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[numbers sendNext:@"1"];
[letters sendNext:@"B"];
[letters sendNext:@"C"];
[numbers sendNext:@"2"];

输出:A1BC2


combineLatest: reduce:

  • 合并多个信号。
  • 触发条件:集齐七颗龙珠才可以召唤一次神龙。
  • 每一个信号只保存最新的值,不是盏结构。
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSignal *combined = [RACSignal
                           combineLatest:@[ letters, numbers ]
                           reduce:^(NSString *letter, NSString *number) {
                               return [letter stringByAppendingString:number];
                           }];
[combined subscribeNext:^(id x) {
    NSLog(@"%@", x);
}];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
[numbers sendNext:@"1"];
//不是盏结构 所以输出B2 而不是A2
[numbers sendNext:@"2"];
[letters sendNext:@"C"];
[numbers sendNext:@"3"];

输出:B1B2C2C3


switchToLatest

  • 使用的场景是signal-of-signalssubscribeNext始终关注并返回最后一个signal的值。之前的signal再有新值则不会触发subscribeNext
RACSubject *letters = [RACSubject subject];
RACSubject *numbers = [RACSubject subject];
RACSubject *signalOfSignals = [RACSubject subject];
RACSignal *switched = [signalOfSignals switchToLatest];
[switched subscribeNext:^(NSString *x) {
    NSLog(@"%@", x);
}];

[signalOfSignals sendNext:letters];
[letters sendNext:@"A"];
[letters sendNext:@"B"];
    
[signalOfSignals sendNext:numbers];
/*!因为先前往signalOfSignals发送了numbers所以最新的信号变成了numbers,这里再往letters发送值则subscribeNext不会触发*/
[letters sendNext:@"C"];
[numbers sendNext:@"1"];
    
[signalOfSignals sendNext:letters];
//这里又切换了最新的signal,所以再往numbers发送值则不会触发subscribeNext。
[numbers sendNext:@"2"];
[letters sendNext:@"D"];

输出:AB1D