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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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