1.Reactive Cocoa解决了什么问题
- 传统iOS开发过程中,状态以及状态之间依赖过多的问题
- 传统MVC架构的问题:Controller比较复杂,可测试性差
- 提供统一的消息传递机制
2.基本概念
一个很好的比喻:
可以把信号想象成水龙头,只不过里面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。这样只要有新的玻璃球进来,就会自动传送给接收方。可以在水龙头上加一个过滤嘴(filter),不符合的不让通过,也可以加一个改动装置,把球改变成符合自己的需求(map)。也可以把多个水龙头合并成一个新的水龙头(combineLatest:reduce:),这样只要其中的一个水龙头有玻璃球出来,这个新合并的水龙头就会得到这个球。
信号(Signal):信号就是流,RAC中所有的信号都是RACStream的子类。信号有冷热之分,信号也有中继、改变等操作。信号是让订阅者去订阅,然后触发对应事件。
- 冷信号(Cold):是被动的信号,只有当你订阅的时候,它才会发布消息。只能一对一,当有不同的订阅者,消息会从新完整发送。
- 热信号(Hot):是主动的信号,即使你没有订阅事件,它仍然会时刻推送。可以有多个订阅者,是一对多,信号可以与订阅者共享信息。
订阅者(Subscriber):负责接收信号执行操作,都必须实现RACSubscriber协议。订阅者之间的关系原则上是平等的,但实际操作上也是有先后顺序,而且还有副作用的情况需要考虑。
事件类型:信号(RACSignal)发送给订阅者(Subscriber)的事件类型有三种,分别是next、error、completed。一个signal在因error终止或者完成前,可以发送任意数量的next事件。三种事件对应函数如下:
subscribeNext:^(id x)
subscribeError:^(NSError *error)
subscribeCompleted:^
有些地方需要注意下,比如把signal作为local变量时,如果没有被subscribe,那么方法执行完后,该变量会被dealloc。但如果signal有被subscribe,那么subscriber会持有该signal,直到signal sendCompleted或sendError时,才会解除持有关系,signal才会被dealloc。
副作用(Side Effect):在每一次订阅的是时候都会触发对应的操作,导致订阅者收到信号的时候,结果会和其他订阅者不同。添加附加操作,如doNext:因为它是附加操作,并不改变事件本身。
生命周期:一个信号的生命周期是由任意个 next 事件和一个 error 事件或一个 completed事件组成的。
Reactive结构图:
3. RACStream
RACStream结构图:
RACStream 是一个抽象类,通常情况下,我们并不会去实例化它,而是直接使用它的两个子类 RACSignal 和 RACSequence 。
RACSignal
RACSignal 并非只有一个类,事实上,它的一系列功能是通过类簇来实现的。
- RACEmptySignal :空信号,用来实现 RACSignal 的 +empty 方法;
- RACReturnSignal :一元信号,用来实现 RACSignal 的 +return: 方法;
- RACDynamicSignal :动态信号,使用一个 block 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
- RACErrorSignal :错误信号,用来实现 RACSignal 的 +error: 方法;
- RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
对于 RACSignal 类簇来说,最核心的方法莫过于 -subscribe: 了,这个方法封装了订阅者对信号源的一次订阅过程,它是<font color = red>订阅者与信号源产生联系的唯一入口</font>。
RACSequence
代表的是一个不可变的值的序列,从严格意义上讲,RACSequence 并不能算作是信号源,因为它并不能像 RACSignal 那样,可以被订阅者订阅,但是它与 RACSignal 之间可以非常方便地进行转换。RACSequence 存在的最大意义就是为了简化 Objective-C 中的集合操作。
从理论上说,一个 RACSequence 由两部分组成:
- head :指的是序列中的第一个对象,如果序列为空,则为 nil ;
- tail :指的是序列中除第一个对象外的其它所有对象,同样的,如果序列为空,则为 nil 。
RACSequence 的一系列功能也是通过类簇来实现的,它共有九个用来实现不同功能的私有子类:
- RACUnarySequence :一元序列,用来实现 RACSequence 的 +return: 方法;
- RACIndexSetSequence :用来遍历索引集;
- RACEmptySequence :空序列,用来实现 RACSequence 的 +empty 方法;
- RACDynamicSequence :动态序列,使用 blocks 来动态地实现一个序列;
- RACSignalSequence :用来遍历信号中的值;
- RACArraySequence :用来遍历数组中的元素;
- RACEagerSequence :非懒计算的序列,在初始化时立即计算所有的值;
- RACStringSequence :用来遍历字符串中的字符;
- RACTupleSequence :用来遍历元组中的元素。
4.RACCommand
RACCommand:代表着与交互后即将执行的一段流程。通常这个交互是UI层级的,比如你点击个Button。RACCommand可以方便的将Button与enable状态进行绑定,也就是当enable为NO的时候,这个RACCommand将不会执行。RACCommand还有一个常见的策略:allowsConcurrentExecution,默认为NO,也就是是当你这个command正在执行的话,你多次点击Button是没有用的。创建一个RACCommand的返回值是一个Signal,这个Signal会返回next或者complete或者error。它有几个比较重要的属性:executionSignals / errors / executing。
- executionSignals是signal of signals,如果直接subscribe的话会得到一个signal,而不是我们想要的value,所以一般会配合switchToLatest。
- errors。跟正常的signal不一样,RACCommand的错误不是通过sendError来实现的,而是通过errors属性传递出来的。
- executing表示该command当前是否正在执行。
5.Reactive Cocoa的使用
combineLatest(聚合信号):把不同信号产生的最新的值聚合在一起,并生成一个新的信号。每次这两个源信号的任何一个产生新值时,reduce block都会执行,block的返回值会发给下一个信号。
Map(转换):Map是接收到信号后,对信号的返回值处理,然后根据需要改变信号的返回值,继续传递该信号。
Injecting effects(注入效果):对信号做一些处理 -doNext: -doError: -doCompleted:。
Filtering(过滤):过滤不符合需求的信号。
FlattenMap(平铺拓扑):收到信号后,对信号返回值进行处理后,返回一个新的信号,可以改变信号的返回值。
Flatten(平铺):当订阅者的数量达到最大当前订阅数的时候,合并接收者发送的各种信号到一个平铺信号中。新的信号会像其他信号一样被排队、订阅。
Concat(拼接):一个信号拼接到另一个信号后面
····
6.RACSignal (Debugging)
/// Logs all events that the receiver sends.
- (RACSignal *)logAll;
/// Logs each `next` that the receiver sends.
- (RACSignal *)logNext;
/// Logs any error that the receiver sends.
- (RACSignal *)logError;
/// Logs any `completed` event that the receiver sends.
- (RACSignal *)logCompleted;
调试辅助函数,可以观察信号的关闭传递等。
附:
函数式编程(Functional Programming):使用高阶函数,例如函数用其他函数作为参数。
响应式编程(Reactive Programming):关注于数据流和变化传播。响应式编程可以避免使用追踪瞬时状态的的实例变量。
RAC()宏:等价于产生信号、传递信号,以及订阅信号。针对某个对象的一个属性进行绑定,信号返回值是对属性的赋值
。
取消信号:在一个completed或者error事件之后,订阅会自动移除;也可以通过RACDisposable 手动移除订阅。
双向绑定:两边任何一边变化,都会对另一边产生影响。eg. RACChannelTo(self, blockShowText) = RACChannelTo(self.viewModel, blockShowText);
FlattenMap和Map的区别:Map是通过FlattenMap来实现的,Map只是改变分发的值。而FlattenMap不仅可以获得上一个信号的值,还能激活下一个信号,形成这种链式反应。Map是FlattenMap在流中信号数量为1,而且信号值是block的返回值的特殊情况。Map是改变一个信号的返回值,FlattenMap是创建一个新的信号。
MVVM概述: