前言
RAC 简介:
对于一个应用来说,绝大部分的时间都是在等待某些事件的发生或响应某些状态的变化,比如用户的触摸事件、应用进入后台、网络请求成功刷新界面等等,而维护这些状态的变化,常常会使代码变得非常复杂,难以扩展。
RAC 的V2.5的类图
关于 ReactiveCocoa 的版本演进历程,简单介绍如下:
<= v2.5 :Objective-C ;
v3.x :Swift 1.2 ;
v4.x :Swift 2.x 。
ReactiveCocoa就是根据 Monad 的概念搭建起来的
一个 Monad 就是一种实现了 Monad typeclass 的数据类型。
Monad :使用 >>= 应用一个接收一个普通值但是返回一个在上下文中的值的函数到一个上下文中的值。
ReactiveCocoa 主要由以下四大核心组件构成:
信号源:RACStream 及其子类;
订阅者:RACSubscriber 的实现类及其子类;
调度器:RACScheduler 及其子类;
清洁工:RACDisposable 及其子类。
信号源又是最核心的部分,其他组件都是围绕它运作的
它使用信号来代表这些异步事件,提供了一种统一的方式来处理所有异步的行为,包括代理方法、block 回调、target-action 机制、通知、KVO 等:
// 代理方法
[[self
rac_signalForSelector:@selector(webViewDidStartLoad:)
fromProtocol:@protocol(UIWebViewDelegate)]
subscribeNext:^(id x) {
// 实现 webViewDidStartLoad: 代理方法
}];
// target-action
[[self.avatarButton
rac_signalForControlEvents:UIControlEventTouchUpInside]
subscribeNext:^(UIButton *avatarButton) {
// avatarButton 被点击了
}];
// 通知
[[[NSNotificationCenter defaultCenter]
rac_addObserverForName:kReachabilityChangedNotification object:nil]
subscribeNext:^(NSNotification *notification) {
// 收到 kReachabilityChangedNotification 通知
}];
// KVO
[RACObserve(self,username) subscribeNext:^(NSString *username) {
// 用户名发生了变化
}];
ReactiveCocoa的作用
ReactiveCocoa为事件提供了很多处理方法
比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式来处理事件响应。其实这些事件,都可以通过RAC处理
利用RAC处理事件:可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
ReactiveCocoa ,就是用信号接管了iOS 中的所有事件;也就意味着,用一种统一的方式来处理iOS中的所有事件。
RAC 的特点
它真正强大的地方在于我们可以对这些不同的信号进行任意地组合和链式操作,从最原始的输入 input 开始直至得到最终的输出 output 为止
[[[RACSignal
combineLatest:@[ RACObserve(self,username),RACObserve(self,password) ]
reduce:^(NSString *username,NSString *password) {
return @(username.length > 0 && password.length > 0);
}]
distinctUntilChanged]
subscribeNext:^(NSNumber *valid) {
if (valid.boolValue) {
// 用户名和密码合法,登录按钮可用
} else {
// 用户名或密码不合法,登录按钮不可用
}
}];
如何导入ReactiveCocoa框架
通常都会使用CocoaPods(用于管理第三方框架的插件)帮助我们导入。
进入终端,建立 Podfile,并且输入以下内容
podfile 具体的内容以GitHub为准。
platform :ios,'8.0'
use_frameworks!
target 'KNTestReactiveCocoa' do
pod 'ReactiveCocoa','~> 4.1.0'
end
版本说明:
2.5 纯 OC 3.0 正式版支持 Swift 1.2 4.0 测试版支持 Swift 2.0
在终端输入以下命令安装框架
$ pod install
注意:
目前只有使用2.5 才能编译成功。
platform :ios,'~> 2.5'
end
ReactiveCocoa常见类
在RAC中最核心的类RACSiganl
RACSiganl:
信号源代表的是随着时间而改变的值流。
RACSignal 代表的是未来将会被传送的值,它是一种 push-driven 的流。RACSignal 可以向订阅者发送三种不同类型的事件:
next :RACSignal 通过 next 事件向订阅者传送新的值,并且这个值可以为 nil ;
error :RACSignal 通过 error 事件向订阅者表明信号在正常结束前发生了错误;
completed :RACSignal 通过 completed 事件向订阅者表明信号已经正常结束,不会再有后续的值传送给订阅者。
通常情况下,一个信号的生命周期是由任意个 next 事件和一个 error 事件或一个 completed 事件组成的。
RACEmptySignal :空信号,用来实现 RACSignal 的 +empty 方法;
RACReturnSignal :一元信号,用来实现 RACSignal 的 +return: 方法;
RACDynamicSignal :动态信号,使用一个 block 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
RACErrorSignal :错误信号,用来实现 RACSignal 的 +error: 方法;
RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
对于 RACSignal 类簇来说,最核心的方法莫过于 -subscribe: 了,这个方法封装了订阅者对信号源的一次订阅过程,它是订阅者与信号源产生联系的唯一入口。因此,对于 RACSignal 的所有子类来说,这个方法的实现逻辑就代表了该子类的具体订阅行为,是区分不同子类的关键所在。
信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发出数据。
注意:
信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅。
RACStream
RACStream 是 ReactiveCocoa 中最核心的类,代表的是任意的值流,它是整个 ReactiveCocoa 得以建立的基石,下面是它的继承结构图:
RACSubscriber
:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;
RACDisposable:
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
它封装了取消和清理一次订阅所必需的工作。它有一个核心的方法 -dispose ,调用这个方法就会执行相应的清理工作,这有点类似于 NSObject 的 -dealloc 方法。RACDisposable 总共有四个子类,它的继承结构图如下:
OmniGraffle 进行绘制类图。
RACSerialDisposable :作为 disposable 的容器使用,可以包含一个 disposable 对象,并且允许将这个 disposable 对象通过原子操作交换出来;
RACKVOTrampoline :代表一次 KVO 观察,并且可以用来停止观察;
RACCompoundDisposable :跟 RACSerialDisposable 一样,RACCompoundDisposable 也是作为 disposable 的容器使用。不同的是,它可以包含多个 disposable 对象,并且支持手动添加和移除 disposable 对象,有点类似于可变数组 NSMutableArray 。而当一个 RACCompoundDisposable 对象被 disposed 时,它会调用其所包含的所有 disposable 对象的 -dispose 方法,有点类似于 autoreleasepool 的作用;
RACScopedDisposable :当它被 dealloc 的时候调用本身的 -dispose 方法。
小结:在合适的时机调用 disposable 对象的 -dispose 方法
RACSubject:
RACSubject 代表的是可以手动控制的信号,我们可以把它看作是 RACSignal 的可变版本,就好比 NSMutableArray 是 NSArray 的可变版本一样。
RACSubject 继承自 RACSignal ,所以它可以作为信号源被订阅者订阅,同时,它又实现了 RACSubscriber 协议,所以它也可以作为订阅者订阅其他信号源,这个就是 RACSubject 为什么可以手动控制的原因。
RACSubject:信号提供者,自己可以充当信号,又能发送信号。
使用场景:通常用来代替代理,有了它,就不必要定义代理了。
在 MVVM 中使用 RACSubject 可以非常方便地实现统一的错误处理逻辑。
—————————————–
另外,RACSubject 也有三个用来实现不同功能的子类:
RACGroupedSignal :分组信号,用来实现 RACSignal 的分组功能;
RACBehaviorSubject :重演最后值的信号,当被订阅时,会向订阅者发送它最后接收到的值;
RACReplaySubject :重演信号,保存发送过的值,当被订阅时,会向订阅者重新发送这些值。
RACReplaySubject:
重复提供信号类,RACSubject的子类。
RACReplaySubject与RACSubject区别:
RACReplaySubject可以先发送信号,在订阅信号,RACSubject就不可以。
使用场景:
一:如果一个信号每被订阅一次,就需要把之前的值重复发送一遍,使用重复提供信号类。
二:可以设置capacity数量来限制缓存的value的数量,即只缓充最新的几个值。
RACSubject和RACReplaySubject简单使用
RACTuple:
元组类,类似NSArray,用来包装值.
RACSequence:
RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
RACCommand
:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
使用场景:监听按钮点击,网络请求
RACMulticastConnection
使用注意:
RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.
RACScheduler:
RAC中的队列,用GCD封装的。
RACUnit :
表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.
RACEvent:
把数据包装成信号事件(signal event)。它主要通过RACSignal的-materialize来使用。
二、 iOS Reactive Cocoa的常见用法
.1 代替代理:
rac_signalForSelector:用于替代代理。
.2 代替KVO :
rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
.3 监听事件:
rac_signalForControlEvents:用于监听某个事件。
.4 代替通知:
rac_addObserverForName:用于监听某个通知。
.5 监听文本框文字改变:
rac_textSignal:只要文本框发出改变就会发出这个信号。
.6 处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals:当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法。
使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
总结
RAC的信号机制很容易将某一个Model变量的变化与界面关联,所以非常容易应用Model-View-viewmodel 框架。通过引入viewmodel层,然后用RAC将viewmodel与View关联,View层的变化可以直接响应viewmodel层的变化,这使得Controller变得更加简单,由于View不再与Model绑定,也增加了View的可重用性。
RAC主要解决了三个问题:
- 传统iOS开发过程中,状态以及状态之间依赖过多的问题
- 传统MVC架构的问题:Controller比较复杂,可测试性差
- 提供统一的消息传递机制。