RAC就像是MVVM的翅膀一样,有了RAC才使得MVVM更加的得心应手。RAC能够使逻辑更加集中,更好处理一些复杂的逻辑。
刚开始接触RAC肯定是一头的雾水,或许你已经上网看过了一些RAC的简单教程,但你依然不懂RACSignal、RACCommand这些到底是什么东西,你可能知道了signal水管的比喻或者是排插的比喻,这些比喻确实是挺确切的,但是一开始学习的时候这些比喻着实还是不太好理解。
RAC一个最核心的功能就是它提供了统一的方法来处理等待值流动。等待值流动?等待值流动就是等待指定事情的发生并且传出相应的值。在IOS开发中等待值流动的表现形式其实的最多的就是异步行为、代理方法,回调blocks,target-action机制,通知和KVO。这些东西的本质其实都是等待值流动,他们都最终告诉你事情发生了然后你处理接下来的逻辑。而在RAC中这些等待值流动统一成了RACSignal,所有的这些等待值流动都可以转成一个RACSignal对象,既然统一了这些值流动的出口,你就不必再零散的写代码,你可以提前并且集中的处理这些值流动以后的逻辑,并且signal具备可操作、可变换、可链接的特性能够更好的统一处理逻辑。
RACSignal其实就是一个能通知指定事情的到来并传递数据给订阅者的一个东东而且有一类RACSubject的signal自己本身就可以充当一个订阅者
接下来我们逐个地把IOS开发中常用的值流动转化为RACSignal
先来小小的窥视一下RACSignal大概是如何实现的,上面的提到了RACSignal能够传递数据给订阅者,代码上实现起来就是简单的消息发送传参数的过程也就是调用订阅者的方法,很显然一个signal的实例中就必须包含着它订阅者的实例才能在流动值到来的时候调用订阅者的方法来为这个订阅者传递数据,看一段signal创建的代码
RACSignal *signal = [RACSignal createSignal:^ RACDisposable * (id subscriber) {
[subscriber sendNext:@"abner"];
[subscriber sendCompleted];
return nil;
}];
创建signal的时候传入一个block,block中要求返回一个 RACDisposable的对象(这个东西后面再说),而block中传给你用的参数是一个实现了RACSubscriber协议的对象,这个协议也就是订阅者协议,也就是说这里传给你使用的参数是一个这个signal的订阅者,至于订阅者为什么要用协议的实现方式是因为在这之中各种各样的实例都可以是订阅者(比如我上面提到的那类signal本身就是一个实现了这个协议的订阅者)。回到这个block本身,这个block里面就是当一个订阅者订阅了这个信号后这个信号自身会做的事情,主要就是把数据传递给这个signal的订阅者,我们把要传递的数据分为三大类并通过不同的方法传参数给订阅者,分别是:
- 普通数据————通过调用订阅者的sendNext:方法来传递普通数据。
- 错误数据————通过调用订阅者的sendError:方法来传递错误数据。
- 完成标志数据————通过调用订阅者的sendComplete方法来传递这次的订阅处理已经完成,解除订阅,不会继续传递数据给这个订阅者,会自动调用订阅的释放以及后续的清理工作。
以上三种类型的数据传递的理解先放一放,我们先来看一下订阅者对一个signal的订阅
[信号 subscriber:订阅者];//调用信号的subscriber方法并传给一个订阅者对象
上面这句伪代码就是订阅者订阅信号,每次有订阅者订阅signal都会执行那个初始化signal时候传入的block从而把signal的值流动传递给订阅者,当时在实际的开发过程中你并不关心订阅者是谁,你关心的只是收到这个值流动以后应该做些什么,所以忘掉订阅者这个东西吧,你更多的时候用到的是下面的代码:
[signal subscribeNext:^(id x) {//普通数据的值出口
NSLog(输出值流动x);
}];
[signal subscribeError:^(NSError *error) {//Error数据的值出口
NSLog(输出值流动x);
}];
[signal subscribeCompleted:^() {//完成标识数据的值出口
}];
上面三个写法的实现其实是用闯入的block创建一个监听者然后通过方法subscriber:
监听signal。至于为什么要把数据分成这三类并且分开出口,我想大多数时候数据的正确的逻辑和数据错误的逻辑都是不一样的,就比如一个网络的请求,正确的数据就会隐藏HUD,刷新UI,而错误的数据则会显示错误信息。既然都谈到了网络请求这个开发当中最大块的异步行为,那接下来就先把网络请求通过上述的signal创建方法转换成signal的形态。直接先上代码
网络请求转换成signal
下面的网络封装以AFNetwork3.0+作为基础,封装为AFHTTPSessionManager的一个category,所以代码中所有的self都可以替换为一个AFHTTPSessionManager的实例对象来理解,相信熟悉AF的童鞋配合注解都能够理解
- (RACSignal *)rac_requestPath:(NSString *)path parameters:(id)parameters method:(NSString *)method {
return [RACSignal createSignal:^(id subscriber) {
NSURLRequest *request = [self.requestSerializer requestWithMethod:method URLString:[[NSURL URLWithString:path relativeToURL:self.baseURL] absoluteString] parameters:parameters error:nil];
NSURLSessionDataTask *task = [self dataTaskWithRequest:request completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
[subscriber sendError:error];//网络异步返回的错误通过这句发送给订阅者
} else {
[subscriber sendNext:RACTuplePack(responseObject, response)];//网络请求成功的正常数据通过这句发送给订阅者
[subscriber sendCompleted];//这一次网络请求结束,不再发送数据,所以发送完成标识
}
}];
[task resume];
return [RACDisposable disposableWithBlock:^{
[task cancel];//订阅完成或者取消,释放资源
}];
}];
}
上面是一段特地简化的代码,在实际开发当中网络请求的正确错误的逻辑走向并不能单单依据请求error来判断,逻辑错误也很有可能走错误的逻辑。可以看出转换过程并不复杂,就是把异步中的block的数据输入输出到订阅者中就可以完成简单的转换了,到这里我们就可以把网络请求全部换成signal的形态了。
KVO转换成signal
你是不是也跟我一样早已经厌倦了传统的KVO的复杂冗长分离的写法,忘记它吧,把KVO转换成signal只需要短短的一行代码,一切能够KVO的属性都能够通过下面的一个RAC封装的宏来转换成相应的signal。
RACObserve(监听目标 , 目标的属性) //这个宏的返回值是一个signal
通知转换成signal
RAC为NSNotificationCenter类做了便捷的把通知转换成signal的扩展
[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"abner" object:nil]; //返回值是一个sinal,把监听名字为abner的通知转换为了信号,一旦发送了名为abner的通知,这个信号就会有值流动。
整容前你的接收通知的代码可能是这样:
-(void)observer {
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(callBack1:) name:@"abner" object:nil];
//结束完后需要删除名字为abner的observer
// [[NSNotificationCenter defaultCenter] removeObserver:self name:@"abner" object:nil];
}
-(void)callBack1:(NSNotification*)notification{
NSString *nameString = [notification name];
NSString *objectString = [notification object];
NSLog(@"name = %@,object = %@",nameString,objectString);
}
这里的逻辑分离程度令人发指,显示注册监听,然后又在外面实现通知到来逻辑,还需要在合适的地方删除这个监听,也就是这一套东西的代码分离在了三个不同的地方。长期以往必然导致精神分裂。而整容以后你的接收通知的代码长这样:
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"abner" object:nil] subscribeNext:^(id x) {
do someing 少年 //动手把你手头上的通知都改成这样吧
}];
target-action机制转换成signal
原生target-action机制的写法也是比较分离的,RAC为UIControl添加的category可以很方便的把target-action转换成signal:
[testButton rac_signalForControlEvents:UIControlEventTouchUpInside]; //返回一个signal,可以通过subscribeNext:来添加入订阅者并处理事件发生后的逻辑。
UIControlEventTouchUpInside事件如此,相信其他事件你也能够无师自通了。
代理机制转换成signal
直接上代码可能你跟容易理解:
[self rac_signalForSelector:@selector(代理方法) fromProtocol:代理协议]; //返回一个signal,前面的self是任意代理指向的对象
其实上面那句代码本质上是当self这个对象触发某个指定方法时候这个signal会产生值流动,它有一个很像的方法如下:
[self rac_signalForSelector:@selector(某个方法)]; //返回一个signal,前面的self是任意监听的对象
貌似到这里基本上所有的东西都变成信号了,利用上面的方法你现在可以把一些常用的东西都转化成信号了:
- UITextField、UITextView的text变化信号---本质上是监听指定方法的调用(UIControlEventAllEditingEvents)和代理方法转信号
- UISearchBar的text变化信号和搜索按下的信号---本质上是代理方法转信号
- UIActionSheet的按钮点击选择的信号---本质上也是代理方法转信号
等等,这些你想的到的想不到的,很庆幸的是大部分的常用的上面提到的信号RAC都已经帮你封装好了,使用的是UIKit的category的形式,我就拿最常用的UITextField的用法说一说,其他的用法基本一样
[textField.rac_textSignal subscribeNext:^(id x){
// 一旦用户通过键盘输入到textField中,这里的block就会触发,并且把text中的值传递下来
}]
至此、貌似你真正的可以把基本上所有的IOS开发中的值流动都转换成signal,这时候你手上拽着一大堆的signal除了能把每个signal都subscribeNext:
然后处理各自signal的逻辑以外貌似别无他用了,signal与signal之间还是各自独立的,并无关联。如果仅仅是这样那RAC也不会被我奉为神器,这个系列的文章更不会叫它终结者了。
回头看看
[textField.rac_textSignal subscribeNext:^(id x){
// 一旦用户通过键盘输入到textField中,这里的block就会触发,并且把text中的值传递下来
}]
这段代码中的注解,“一旦用户通过键盘输入“,我写注解都是很严谨的为什么这里要强调键盘输入,这也是我一开始接触RAC的时候掉过的一个坑,除了键盘输入还有一中方法能改变UITextField中显示的内容,就是通过Copy,Paste进来的值,你不妨去盯一下,这个时候其实UIControlEventAllEditingEvents这个事件是不触发的,通过Copy,Paste进来的值会直接改变UITextField的text属性的值,也就是说这种情况下只能用[RACObserve(textField,text)]
的方式来转换成信号,但是大多数情况下这两种signal的值流动的处理逻辑是一样的,如果有两个signal的话那处理逻辑就要写两遍或者调两次同一处理逻辑,这样就导致逻辑分散,所以我们就想能不能把这两个signal先揉成一个signal,然后再subscribe:
这个signal统一处理逻辑,你能这么想就说明你能理解了signal到底是个什么样的东西了,接下来我们就说说signal与signal的Operations,或许把它翻译理解为signal的运算。见终结者(三)