ReactiveCocoa 是一个符合 FRP(Functional Reactive Programming)范式的,操作和转换数据流的框架。
它可以解决三个问题:
- 开发过程中关于界面的状态信息太多,不易维护。
- 统一各种消息传递的方式。
- 绑定功能可以配合 MVVM 使用。
基本概念
简而言之,Singal 以 stream 的方式将 event 发送给 Observer。
Event
Event 代表值发生变化,或者有用户操作,分为 4 种:
- Next:signal 产生了新的值。
- Failed:signal 执行发生了错误,不再产生新的值。
- Completed:signal 执行结束,不再产生新的值。
- Interrupted:signal 被中止,不再产生新的值。
Singal
Signal 类似于一个 event 源头,event 通过 signal 传递。用户只能通过 subscribe 来开始有序地、被动地获取 signal 里面的值。一一个 signal 的生命周期通常是一个或多个 Next event,然后以 Failed、Completed 和 Interrupted 中的一种 event 结束,不受 subscriber 的影响。
用户可以使用基本操作符对 Signal 进行一些操作,如过滤(filter)、映射(map)等。
Observer
通过 subscribe 一个 signal 来接受 event 的对象。
RACCommand
通常用来执行一个 UI 事件发生后要处理的任务。
RACSubject
一个 signal 的子类,是一个可以手动发送 Next、Failed 等事件的 signal。通常用来将 non-RAC 的代码 bridge 成 RAC 的代码。
基本操作
添加副作用
-subscribe...:
用来订阅一个 signal 的 Next、Failed、Completed 和 Interrupted 事件中的一种或几种:
1 | RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; |
-do...:
用来添加副作用,当 Next、Failed、Completed 和 Interrupted 事件中的一种发生时执行 do...
中的 block。
1 | __block unsigned subscriptions = 0; |
转换 stream
-map:
将一个 stream 中的值转换成其他的值,组成一个新的 stream。
1 | RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; |
-filter:
用来过滤 stream 中的值,传入的 block 返回一个 BOOL,来决定是否将值放入新的 stream 中。
1 | RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; |
组合 stream
-concat:
将一个 stream 的值追加到另一个 stream 的值后面。
1 | RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; |
-flatten:
如果一个 signal 的 stream 中的值也是一个 stream,-flatten:
会将子 stream 中的值取出,组成一个新的 stream。组合的效果可能是 concat:
1 | RACSequence *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence; |
或者 merge:
1 | RACSubject *letters = [RACSubject subject]; |
-flattenMap:
当一个 stream 的值也是 stream 时,将子 stream 中的值转换后,组成一个新的 stream。
-flattenMap:
= -map:
+ -flatten:
1 | RACSequence *numbers = [@"1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "].rac_sequence; |
组合 signal
-then:
当一个 signal 执行完成之后,subscriber 后续只会收到新返回的 signal 的值,如同订阅了新的 signal 一样。
1 | RACSignal *letters = [@"A B C D E F G H I" componentsSeparatedByString:@" "].rac_sequence.signal; |
+merge:
将多个 signal 合并为一个,一旦任一合并前的 signal 中产生新的值,新的 signal 立即产生同样的值。
1 | RACSubject *letters = [RACSubject subject]; |
+combineLatest:
和+combineLatest:reduce:
将多个 signal 合并成一个。与 +merge:
不同的是,这两个方法产生的新的 signal 只有在所有的合并前 signal 至少产生一个新的值时,才将所有的值(以元组的方式)返回。
1 | RACSubject *letters = [RACSubject subject]; |
-switchToLatest
用来处理由 signal 组成的 signal (signal-of-signals),总是返回最新的外层 signal 中的最新的 signal 的值。
1 | RACSubject *letters = [RACSubject subject]; |
其他
take:(NSUInteger)n
仅仅值的前 n 次的变化会继续传递下去。
distinctUntilChanged
仅当 signal 的值与上一次不同时才会继续传递下去。
throttle:(NSTimeInterval)interval
在 interval 的时间内发生的值的变化仅传递最后一个。
统一消息传递机制
ReactiveCocoa 统一了 iOS 中几乎所有的消息传递方式:
- Blocks
- Delegates
- Notifications
- Errors
- Target-Action
- KVO
- Method Overriding
1 | // Blocks -> Signal |
UIView Categories
ReactiveCocoa 提供了许多 UIView 的 Category 用来快速将 UI 的事件、属性转换成 signal。例如 rac_textSignal
是 UITextField 的 text 属性转换成的信号;rac_buttonClickedSignal
是 UIAlertView 的按钮点击的信号。
我们订阅这些信号就可以摆脱各种 Delegate。
1 | UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:@"" message:@"Alert" delegate:nil cancelButtonTitle:@"YES" otherButtonTitles:@"NO", nil]; |
宏
ReactiveCocoa 提供了一些非常方便的宏。
RACObserve(self, status)
:将 status 转换成一个 signal,当其值发生变化时触发事件。作用与 KVO 相同。RAC(self.textField, text)
:将某个对象的某个属性绑定到一个 signal 的值上。@weakify(self)
和@strongify(self)
用于避免循环引用。
Ref
- http://www.infoq.com/cn/articles/functional-reactive-programming
- https://github.com/ReactiveCocoa/ReactiveCocoa
- http://www.sprynthesis.com/2014/06/15/why-reactivecocoa/
- http://www.infoq.com/cn/articles/reactivecocoa-ios-new-develop-framework
- https://medium.com/@syshen/reactivecocoa-in-practice-4f04119efc68#.z0m1z8h1q