ReactiveCocoa的一些特性细节
2018-11-10 本文已影响106人
iOS入门级攻城尸
本文基于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
- 设置
doNext
或doCompleted
回调函数后会得到一个新的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);
}];
输出:AA
、BB
、CC
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);
}];
输出:2
、4
、6
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);
}];
输出:A
、B
、C
、1
、2
、3
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);
}];
输出:A
、B
、C
、1
、2
、3
flatten for Signal
- 实际作用是
merge
将多个signal
合并成一个。 - 合并后任何一个
signal
发送数据,都会触发signal
的block
执行。
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
- 相当于先
map
再flatten
。
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);
}];
输出:1
、1
、2
、2
、3
、3
ps:这里不是输出11
、22
、33
这里没有字符串拼接,而是被拍扁了。
- 在对
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"];
输出:A
、1
、B
、C
、2
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"];
输出:B1
、B2
、C2
、C3
switchToLatest
- 使用的场景是
signal-of-signals
,subscribeNext
始终关注并返回最后一个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"];
输出:A
、B
、1
、D