ReactiveCocoa------ReactiveCocoa高阶操作

引言

延续上一篇ReactiveCocoa操作详解,本篇笔者围绕针对RAC信号的维度变换(升阶、降阶)进行深入探究,同时在文章结尾给出Functor、Applicatives和Monad的概念。

升阶 & 降阶

ReactiveCocoa简介中,笔者提到类似C语言中的多维数组,RAC中同样存在多维信号的概念。多维信号发送的每一个值都是一个信号,为方便后续交流笔者将其称为“值信号”。本篇中以大写英文字母“A、B、C”表示值信号,以阿拉伯数字“1、2、3”表示普通值。

高阶信号

高阶信号的创建

通过return创建一个返回信号的信号。

1
2
RACSignal *signal = [RACSignal return:@1];
RACSignal *signalHighOrder = [RACSignal return:signal];

通过map变换返回一个信号,达到对信号升阶的目的。

1
2
3
RACSignal *anotherSignal = [signal map:^id(id value) {
return [RACSignal return:value];
}];

高阶信号的订阅

通过嵌套订阅拿到真正的值。

1
2
3
4
5
[highOrderSignal subscribeNext:^(RACSignal *aSignal) {
[aSignal subscribeNext:^(id x) {
// get real value here.
}];
}];

降阶操作

通过map升阶将值转换为值信号,在实际应用中最终还是需要获得值。RAC为我们提供了多种降阶操作以满足不同的场景。

SwitchToLatest

switchToLatest操作要求原信号必须是一个高阶信号(信号的信号),即信号的值必须是一个信号。switchToLatest将原信号输出的最新的值信号的next事件和error事件作为新信号的next时间和error事件输出。

Next事件:输出原信号输出的最新值信号的next事件。
Error事件:输出原信号输出的最新值信号的error事件、同时会在原信号输出error事件。
Completed事件:取原信号的completed事件和原信号的最后一个值信号的completed事件的在时间维度上较晚发出者。

图例

Coding

为突出信号A、B、C在时间维度发送事件的先后顺序,这里使用RACSignal的子类代替RACSubject。RACSubject与RACSignal稍有不同,在后续冷热信号中会详细探讨。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RACSubject * signalOfSignals = [RACSubject subject];
RACSubject * signalA = [RACSubject subject];
RACSubject * signalB = [RACSubject subject];
RACSubject * signalC = [RACSubject subject];

[[signalOfSignals switchToLatest] subscribeNext:^(id x) {
NSLog(@"%@",x);//output 1 3 5 7 8
}];

[signalOfSignals sendNext:signalA];
[signalA sendNext:@1];
[signalOfSignals sendNext:signalB];

[signalA sendNext:@2];
[signalB sendNext:@3];
[signalA sendNext:@4];

[signalOfSignals sendNext:signalC];
[signalA sendCompleted];
[signalC sendNext:@5];
[signalB sendNext:@6];
[signalB sendCompleted];
[signalOfSignals sendCompleted];
[signalC sendNext:@7];
[signalC sendNext:@8];
[signalC sendCompleted];

If:then:else

If:then:else操作基于boolSignal发送的最新值,在tureSignal、falseSignal之间切换。
要求boolSignal的值必须为NSNumber类型,且boolSignal、tureSignal、falseSignal均不为nil。
If:then:else操作其实是一个语法糖,底层使用switchToLatest实现。

Next事件:boolSignal发送ture,输出tureSignal信号的next事件;boolSignal发送false,输出falseSignal信号的next事件。
Error事件:boolSignal发送ture,输出tureSignal信号的error事件;boolSignal发送false,输出falseSignal信号的error事件。
Completed事件:当boolSignal与当前选中信号都发送completed事件后,发送completed事件。

图例

Coding

为突出时间概念同样使用RACSubject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
RACSubject * boolSignal = [RACSubject subject];
RACSubject * signalTure = [RACSubject subject];
RACSubject * signalFalse = [RACSubject subject];
RACSignal * resultSignal = [RACSignal if:boolSignal then:signalTure else:signalFalse];

[resultSignal subscribeNext:^(id x) {
NSLog(@"%@",x);//output 1 2 c d 5 6 g h
}];

[boolSignal sendNext:@YES];
[signalTure sendNext:@1];
[signalTure sendNext:@2];
[signalFalse sendNext:@"a"];
[signalFalse sendNext:@"b"];

[boolSignal sendNext:@NO];
[signalTure sendNext:@3];
[signalTure sendNext:@4];
[signalFalse sendNext:@"c"];
[signalFalse sendNext:@"d"];


[boolSignal sendNext:@YES];
[signalTure sendNext:@5];
[signalTure sendNext:@6];
[signalFalse sendNext:@"e"];
[signalFalse sendNext:@"f"];

[boolSignal sendNext:@NO];
[signalTure sendNext:@7];
[signalTure sendNext:@8];
[signalFalse sendNext:@"g"];
[signalFalse sendNext:@"h"];

[boolSignal sendCompleted];
[signalFalse sendCompleted];
[signalTure sendCompleted];

Concat

concat操作要求原信号必须是一个高阶信号(信号的信号),即信号的值必须是一个信号。
链接原信号发送的所有值信号,注意与ReactiveCocoa操作详解concat:相区分 。

Next事件:依次发送值信号的next事件,在当前订阅值信号发送completed事件后,开始发送下一个值信号的next事件。
Error事件:输出值信号的error事件,同时会在原信号输出error事件。
Completed事件:原信号和当前订阅值信号均发送completed事件后,输出completed事件。

图例

Coding

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RACSignal * signalOfSignals = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
RACSignal * signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
[subscriber sendNext:@3];
[subscriber sendNext:@4];
[subscriber sendCompleted];
return nil;
}];

RACSignal * signalB = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"a"];
[subscriber sendNext:@"b"];
[subscriber sendNext:@"c"];
[subscriber sendNext:@"d"];
return nil;
}] deliverOn:[RACScheduler scheduler]] ;

[subscriber sendNext:signalA];
[subscriber sendNext:signalB];
return nil;
}];

[[signalOfSignals concat] subscribeNext:^(id x) {
NSLog(@"%@",x);//output 1 2 3 4 a b c d
}];

学以致用

利用concat实现1秒延时信号

1
2
3
4
RACSignal *signal = @[@1, @3, @7, @9, @8].rac_sequence.signal;
RACSignal *timerSignal = [[signal map:^id(id value) {
return [[RACSignal return:value] delay:1];
}] concat];

Flatten

flatten操作要求原信号必须是一个高阶信号(信号的信号),即信号的值必须是一个信号。
对原信号进行降阶操作,对原信号输出的值信号输出的所有next事件、error事件作为新信号的事件按时间顺序依次输出。

Next事件:按时间顺序依次输出原信号的值信号的next事件。
Error事件:输出原信号的值信号的error事件。
Completed事件:在所有信号均输出completed事件后输出completed事件。

图例

Coding

为突出时间概念同样使用RACSubject

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
RACSubject * signalOfSignals = [RACSubject subject];
RACSubject * signalA = [RACSubject subject];
RACSubject * signalB = [RACSubject subject];
RACSubject * signalC = [RACSubject subject];

[[signalOfSignals flatten] subscribeNext:^(id x) {
NSLog(@"%@",x);//output 1 3 5 7 8
}];

[signalOfSignals sendNext:signalA];
[signalA sendNext:@1];
[signalOfSignals sendNext:signalB];

[signalA sendNext:@2];
[signalB sendNext:@3];
[signalA sendNext:@4];

[signalOfSignals sendNext:signalC];
[signalA sendCompleted];
[signalC sendNext:@5];
[signalB sendNext:@6];
[signalB sendCompleted];
[signalOfSignals sendCompleted];
[signalC sendNext:@7];
[signalC sendNext:@8];
[signalC sendCompleted];

FlattenMap

不止局限于RAC(例如:RxJava),flattenMap在整个函数式编程中也是一个重要的概念。以字面理解flattenMap是flatten操作和map操作的结合,事实上也的确如此。

Flatten && Map

flattenMap的本质是map然后flatten:它首先利用map将值映射为信号(升阶),然后利用flatten进行降阶。这里只是说明功能,flattenMap的实现并不是通过调用map+flatten实现的。实质上flattenMap的map是通过在block生成中间信号M,flatten是在内部对中间信号进行订阅实现的。后续会对RAC源码进行更深入探究,这里只说明功能。

举个栗子

考虑将一个无效值转换为error事件,不止局限于理论在实际开发中我们也经常碰到这样的需求。

Flatten && Map组合实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RACSignal * signal = @[@1,@2,@3,@0].rac_sequence.signal;

RACSignal * mappedSignal = [[signal map:^id(NSNumber * value) {
if (value.integerValue == 0) {
return [RACSignal error:[NSError errorWithDomain:@"bosskai.com" code:-1 userInfo:nil]];
}else{
return [RACSignal return:value];
}
}]flatten] ;

[mappedSignal subscribeNext:^(id x) {
NSLog(@"%@",x); // output 1 2 3
} error:^(NSError *error) {
NSLog(@"%@",error); // Error Domain=bosskai.com Code=-1 "(null)"
}];
FlattenMap实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
RACSignal * signal = @[@1,@2,@3,@0].rac_sequence.signal;

RACSignal * mappedSignal = [signal flattenMap:^RACStream *(NSNumber * value) {
if (value.integerValue == 0) {
return [RACSignal error:[NSError errorWithDomain:@"booskai.com" code:-1 userInfo:nil]];
}else{
return [RACSignal return:value];
}
}];

[mappedSignal subscribeNext:^(id x) {
NSLog(@"%@",x); // output 1 2 3
} error:^(NSError *error) {
NSLog(@"%@",error); // Error Domain=bosskai.com Code=-1 "(null)"
}];

实际开发中其实有很多类似的需求,FlattenMap也提供了完美的支持。利用FlattenMap可以轻松的完成信号间的转换,而且FlattenMap是支持串行异步操作的。那么为什么说FlattenMap在函数是编程中也是一个重要的概念呢?因为FlattenMap满足Monad。

Functor、Applicatives和Monad

Functor、Applicatives和Monad是函数式编程汇总三个非常重要的术语。计算机科学习惯于为抽象概念命名术语,我们也从这些术语中获益良多。这些术语使我们在交流中能够引用抽象概念,并立即使对方知道我们的意思,比如我们从设计模式的共享名称(工厂、装饰器等)中获益良多。其中一些术语非常抽象,如:Functor、Applicatives和Monad。

本篇中笔者从结论出发让大家有一个大概的印象,在之后的一篇post中会从swift出发为大家做详细剖析。我们可以将Functor、Applicatives和Monad理解为协议,协议内容为满足某种操作。

Functor: 应用一个函数到封装后的对象,如RAC中的map。
Applicatives:应用一个封装后的函数到一个封装后的对象。RAC并未提供对Applicatives的支持。
Monad:应用一个返回封装后的对象的函数到一个封装后的对象。RAC中的flattenMap。

封装后的对象我们可以理解为,RAC中的Signal、swift中的Optional。有点绕~,先有个概念。