本文会有浅入深,介绍ReactiveCocoa为何深受iOS开发工程师的喜爱。以及简单的使用ReactiveCocoa完成一些基本的UI交互。
从最基本的开始,button有一些点击事件,我们通常是这样做的:
然后再moreButtonPressed中添加需要执行的操作。这样显然会把要监听的事件和处理的事件隔离开来。
不是很方便我们找到相应的代码处理块。这是不符合高聚合,低耦合的编程思想的。
那么高端一点的会怎样解决这个问题呢?下边是升级的版本,请观察:
这样可以把要处理的事情,和监听的事情的代码放在一起。很容易就找到要处理的按钮的点击事件。
这是为了实现高聚合,低耦合的编程思想而封装的扩展类,基本都是block方法中处理点击,长按手势和滑动操作等事件。
那么下边就需要介绍,根据这种编程思想,衍生出来的ReactiveCocoa框架。它通过信号量和订阅者的方式,实现了交互逻辑。
2.ReactiveCocoa作用
在我们iOS开发过程中,经常会响应某些事件来处理某些业务逻辑,例如按钮的点击,上下拉刷新,网络请求,属性的变化(通过KVO)或者用户位置的变化(通过CoreLocation)。但是这些事件都用不同的方式来处理,比如action、delegate、KVO、callback等。
其实这些事件,都可以通过RAC处理,ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
Signal信号是数据流,可以被绑定和传递。可以把信号想象成水龙头,只不过里 面不是水,而是玻璃球(value),直径跟水管的内径一样,这样就能保证玻璃球是依次排列,不会出现并排的情况(数据都是线性处理的,不会出现并发情况)。水龙头的开关默认是关的,除非有了接收方(subscriber),才会打开。
这样只要有新的玻璃球进来,就会自动传送给接收方。接收方就是放在水龙头下的盆子,对于水龙头不同的出水状况,有自己的处理方式。水龙头出水时会通知下方的水盆,如果没有水盆,水龙头始终处于关闭状态。
信号Signal是ReactiveCocoa核心部件之一。有信号选择器内置UIKit组件。例如,有一个rac_textsignal UITextField的值与在文本字段中的每一个新的按键发送。我们将在下一节中看到如何使用信号来执行工作。
信号也可以被链接和转化。当我们映射或过滤一个流时,我们创建一个新的流。随后这流被映射,过滤,随意摆弄都没问题。我们会看到更多的关于下一章的链接。
# 处理逻辑 · 数据内容
#订阅
处理逻辑指的是创建信号的时候,它是如何通知订阅者(Subscriber)并选择发送何种事件的。
数据内容指的是信号会传递给订阅者(Subscriber)什么样的数据。这就像一个水龙头,它什么时候告诉水盆自己正在滴水,或是已经滴完水了。以及它把什么丢入水盆,是原始的水滴,还是水滴的质量?
如果我们需要对这些内容进行自定义的修改,那么修改原信号显然是不可行的(信号已经被创建了)。因此,这就牵涉到信号之间的转换(Map)与组合(Combine)。
我们从RACSignal类最基础的方法开始讨论信号之间的转换(Map)与组合(Combine)。对于每一个方法,我们需要关注这个方法的功能、返回值类型。由于ReactiveCocoa大量使用了block,还需要关注方法中block的参数类型和返回值类型。
订阅是在数据流上进行的,通常是信号,当你想被通知时,一个新的值被发送(或下一个错误,或完成)。信号经常被用来产生一些其他的作用,例如下面的例子。
让我们添加一个文本字段的视图控制器的视图,并将它连接到一个IBOutlet。我要使用内置的脚本和助理编辑这样做,但你做任何你喜欢做的事。
#导出状态
导出状态中的另一个核心部件ReactiveCocoa。我们可以将属性抽象成一个流,而不是在一个类中有一个属性设置为新的值。让我们以前一个例子,并用派生的状态来扩充它。
让我们假装我们的观点是一种形式为创建一些帐户,我们只希望允许电子邮件地址,包含一个' @ '符号。当,只有当一个有效的用户名已输入,然后一个按钮的启用状态将是肯定的。我们还想提供反馈给用户的文本颜色的文本字段的方式。
让我们首先添加按钮,创建一个IBOutlet。
[self.textField.rac_textSignal subscribeNext:^(id x){
NSLog(@"New value: %@", x);
}error:^(NSError*error){
NSLog(@"Error: %@", error);
}completed:^{
NSLog(@"Completed.");
}];
//当输入文本中不包含@字符的时候,是不允许用户创建账户的。这就是map的功能。
// RAC(self.button,enabled)=[self.textField.rac_textSignal map:^id(NSString *value){
// return @([value rangeOfString:@"@"].location != NSNotFound);
// }];
/*
RAC()宏需要两个参数:一个对象,该对象的一个关键路径。然后,它执行一个单向绑定的表达式的右手值的关键路径问题。值必须NSObjects,这就是NSNumber布尔表达式。
但是文字颜色呢?我们实际上可以重新使用的信号从早期的一点点重构。
*/
RACSignal *validEmailSignal=[self.textField.rac_textSignal map:^id(NSString *value){
return @([value rangeOfString:@"@"].location != NSNotFound);
}];
// RAC(self.button,enabled)= validEmailSignal;
// RAC(self.textField,textColor)=[validEmailSignal map:^id(id value){
// if ([value boolValue]) {
// return [UIColor orangeColor];
// } else {
// return [UIColor redColor]; }
// }];
/*
当您希望在响应于用户交互事件时发出的信号值时,命令是有用的。该命令的信号可以订阅到稍后处理我们返回的信号的输出。
现在,替换在我们的按钮上启用的属性的绑定到以下。
按下按钮,信号块被执行时,和rac_command财产照顾绑定启用信号按钮的启用状态(事实上,如果我们把我们的原代码,我们将引起错误两绑定到同一个属性)。
但是返回值是什么呢,我们需要返回一个信号将被发送到executionsignals管属于raccommand。这让您返回一个信号,表示一些工作,按结果更改按钮的相关操作。该按钮将继续被禁用,直到该信号返回其完整的值(立即返回此值为空)。因为我们只会记录下按钮的结果,所以在这种情况下,我们返回一个空信号。
*/
self.button.rac_command=[[RACCommand alloc] initWithEnabled:validEmailSignal signalBlock:^RACSignal *
(id input){
NSLog(@"Button was pressed.");
return [RACSignal empty];
}];
RACSignal
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
_subscriber = subscriber;
//当订阅者被强引用,且没有被销毁,那么释放信号的方法就不会被执行
[subscriber sendNext:@(1)];
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号被取消");
}];
return nil;
}];
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//手动取消订阅,即使订阅者没有被销毁,仍然可以取消该信号
[disposable dispose];
RACSubject
racsubject是一个有趣的信号类型。这是ReactiveCocoa创建的“可变状态“世界。这是一个信号,你可以手动发送新的值。因为这个原因,除了在特定的情况下,它是不推荐使用的。
Hot and Cold Signals 热信号和冷信号
信号在通常情况下是懒惰的,我们可以称之为懒加载。只有当他们被订阅的时候,才去发送信号和执行相应的操作。每添加一个新的订阅者,就需要重新去执行相应的操作。对于琐碎的操作来说,这是合理的也是可取的。我们称这类信号为热信号。
然而,有时,我们希望工作立即完成。这种类型的信号被称为热信号。使用热信号是非常罕见的。
//既能创建信号,也能发送信号
RACSubject *subject = [RACSubject subject];
//创建订阅者
[subject subscribeNext:^(id x) {
NSLog(@"订阅者1接收到数据:%@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"订阅者2接收到数据:%@",x);
}];
//发送数据 遍历所有的订阅者,然后发送数据
[subject sendNext:@3];
//不同信号的订阅方式不同,请谨慎
RACReplaySubject
// RACReplaySubject 可以先发送数据,然后订阅,不同于上边的信号类型
RACReplaySubject *replaySubject = [RACReplaySubject subject];
//订阅方式不一样,遍历所有的值,拿到当前的订阅者,去发送
[replaySubject subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//sendNext :保存数据,遍历所有的订阅者,发送数据
[replaySubject sendNext:@(1)];
//RAC 集合类
NSArray *array = [NSArray arrayWithObjects:@"213",@"123",@"23333",@"212323", nil];
RACSequence *sequence = array.rac_sequence;
RACSignal *arraySignal = sequence.signal;
//订阅集合信号,内部会自动遍历所有元素
[arraySignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[array.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"%@ %@",key,value);
}];
//1:代替代理 2:代理KVO 3: 代替监听事件 4: 代替通知 5:监听文本框文字变化 6: 处理界面有多次请求时候
Rac_liftSelector
//这里重点讲解一下 当界面需要多个网络请求的时候,需要全部获取数据后显示界面时,才显示该界面。
//当数组中的数据都发送的时候,才调用该方法
RACSignal *hotSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hot"];
return nil;
}];
RACSignal *favouriteSignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"favourite"];
return nil;
}];
[self rac_liftSelector:@selector(reloadData:favourite:) withSignals:hotSignal,favouriteSignal, nil];
//传递的参数,即是我们信号发送的数据类型 [subscriber sendNext:@"favourite"]; [subscriber sendNext:@"hot"];
- (void)reloadData:(NSString *)hotString favourite:(NSString *)favouriteString{
NSLog(@"数据都请求完毕");
}
RACMulticastConnection
多播
多播是指在任何一个订阅用户数之间共享的一个术语。信号,默认情况下,是冷的(如我们所描述的最后一节)。当它被订阅时,不希望有一个寒冷的信号,执行工作的每一次操作。
所以我们创造了从信号的racmulticastconnection。你可以通过使用公开发布racsignal,或组播方法。前一种方法为您创建一个多播连接。后者也是这样做。这个问题上是手动从底层发送信号时,它被称为发送的值。然后,而不是任何有兴趣的潜在信号发送的值订阅连接的信号
//每次订阅不要都请求数据,只需要拿到数据就可以了,不管订阅多少次,都只请求一次 把信号转化为连接类,订阅连接类的信号
//把信号转化为连接类
RACMulticastConnection *multicastConnection = [signal publish];
//订阅连接类信号
[multicastConnection.signal subscribeNext:^(id x) {
NSLog(@"连接类1%@",x);
}];
[multicastConnection.signal subscribeNext:^(id x) {
NSLog(@"连接类2%@",x);
}];
[multicastConnection.signal subscribeNext:^(id x) {
NSLog(@"连接类3%@",x);
}];
//连接
[multicastConnection connect];
RACCommend
//RACCommend 处理事件,把事件数据都包装在累中处理,很方便的监听事件是否执行完成 不能返回一个空的信号
RACCommand *commend = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"%@",input); //只要执行命令就会调用该方法。
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
//发送数据
[subscriber sendNext:@"执行命令产生的数据"];
//发送完成
[subscriber sendCompleted];
return nil;
}];
}];
//监听事件是否完成 当前命令内部发送数据完成,一定要主动发送完成
[commend.executing subscribeNext:^(id x) {
if ([x boolValue]== YES) {
NSLog(@"当前正在执行");
}else{
NSLog(@"执行完成/还没有执行");
}
}];
//executionSignals:信号源,信号中的信号,发送的数据就是信号,而不是一些string,数组之类的,commend.executionSignals 必须在执行之前就订阅
[commend.executionSignals subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//获取最新发送的信号,只能用于信号中的信号 switchToLatest 信号中的信号发送的最新数据
[commend.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//如何拿到执行命令中产生的数据 订阅命令内部的信号 @1 订阅信号 @订阅信号
RACSignal *commendSignal = [commend execute:@(1)];
[commendSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
RAC bind:^RACStreamBindBlock]
//RACBind信号
RACSignal *bindSignal = [subject bind:^RACStreamBindBlock{
return ^RACSignal *(id value, BOOL *stop){
//block调用:只要源信号发送数据,就会调用, 处理源信号数据
NSLog(@"%@",value);
value = [NSString stringWithFormat:@"MMM%@",value];
//返回信号,不能为nil
return [RACReturnSignal return:value];
};
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@(2)];
RAC flattenMap 绑定信号
RACSignal *flattenMapSignal = [subject flattenMap:^RACStream *(id value) {
//返回信号用来包装成修改的内容
return [RACReturnSignal return:value];
}];
//绑定信号中返回什么信号,订阅的就是什么信号
//订阅信号
[flattenMapSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//发送数据
[subject sendNext:@"YES"];
RAC mapSignal 绑定信号
RACSignal *mapSignal = [subject map:^id(id value) {
return @"YES";
}];
[mapSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"YES"];
信号之间的组合 concat
在开发中,经常用RACSignal 发送网络请求
信号之间的组合 concat [subscriber sendCompleted];
RACSignal *requestSignalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"网络请求1"];
//请求完成 然后请求下边的网络请求
[subscriber sendCompleted];
return nil;
}];
RACSignal *requestSignalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"网络请求2"];
return nil;
}];
RACSignal *concatSignal = [requestSignalA concat:requestSignalB];
[concatSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
信号之间的组合 then
RACSignal *thenSignal = [requestSignalA then:^RACSignal *{
return requestSignalB;
}];
[thenSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
信号的超时,延迟,重试
RACSignal *signal = [self rac_signalForSelector:@selector(commandAction:)];
[[[RACSignal interval:1 onScheduler:[RACScheduler scheduler]]takeUntil:signal] subscribeNext:^(id x) {
NSLog(@"%@",[NSDate date]);
}];
RACSignal *delaySignal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"2"];
return [RACDisposable disposableWithBlock:^{
}];
}];
[[delaySignal delay:2] subscribeNext:^(id x) {
NSLog(@">>>>>>%@",[NSDate date]);
[self commandAction:nil];
}];
[[delaySignal retry] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
NSLog(@"%@",error);
}];
Functional Reactive Programming 文档: http://pan.baidu.com/s/1o61mB4e
其他一些关于ReactiveCocoa的博客地址: http://www.mamicode.com/info-detail-661152.html
相关唐巧:http://blog.devtang.com/blog/2016/01/03/reactive-cocoa-discussion/
探究RAC-RAC信号处理方法归纳 :http://cbsfly.github.io/ios/rac2/?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io