引言
延续上一篇ReactiveCocoa操作详解,本篇笔者围绕针对RAC信号的维度变换(升阶、降阶)进行深入探究,同时在文章结尾给出Functor、Applicatives和Monad的概念。
升阶 & 降阶
在ReactiveCocoa简介中,笔者提到类似C语言中的多维数组,RAC中同样存在多维信号的概念。多维信号发送的每一个值都是一个信号,为方便后续交流笔者将其称为“值信号”。本篇中以大写英文字母“A、B、C”表示值信号,以阿拉伯数字“1、2、3”表示普通值。
高阶信号
高阶信号的创建
通过return创建一个返回信号的信号。
1 | RACSignal *signal = [RACSignal return:@1]; |
通过map变换返回一个信号,达到对信号升阶的目的。
1 | RACSignal *anotherSignal = [signal map:^id(id value) { |
高阶信号的订阅
通过嵌套订阅拿到真正的值。
1 | [highOrderSignal subscribeNext:^(RACSignal *aSignal) { |
降阶操作
通过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 | RACSubject * signalOfSignals = [RACSubject subject]; |
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 | RACSubject * boolSignal = [RACSubject subject]; |
Concat
concat操作要求原信号必须是一个高阶信号(信号的信号),即信号的值必须是一个信号。
链接原信号发送的所有值信号,注意与ReactiveCocoa操作详解concat:相区分 。
Next事件:依次发送值信号的next事件,在当前订阅值信号发送completed事件后,开始发送下一个值信号的next事件。
Error事件:输出值信号的error事件,同时会在原信号输出error事件。
Completed事件:原信号和当前订阅值信号均发送completed事件后,输出completed事件。
图例
Coding
1 | RACSignal * signalOfSignals = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { |
学以致用
利用concat实现1秒延时信号
1 | RACSignal *signal = @[@1, @3, @7, @9, @8].rac_sequence.signal; |
Flatten
flatten操作要求原信号必须是一个高阶信号(信号的信号),即信号的值必须是一个信号。
对原信号进行降阶操作,对原信号输出的值信号输出的所有next事件、error事件作为新信号的事件按时间顺序依次输出。
Next事件:按时间顺序依次输出原信号的值信号的next事件。
Error事件:输出原信号的值信号的error事件。
Completed事件:在所有信号均输出completed事件后输出completed事件。
图例
Coding
为突出时间概念同样使用RACSubject
1 | RACSubject * signalOfSignals = [RACSubject subject]; |
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 | RACSignal * signal = @[@1,@2,@3,@0].rac_sequence.signal; |
FlattenMap实现
1 | RACSignal * signal = @[@1,@2,@3,@0].rac_sequence.signal; |
实际开发中其实有很多类似的需求,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。有点绕~,先有个概念。