使用RAC其实就是一个创建信号订阅信号的过程。上篇ReactiveCocoa函数响应式编程-基础篇,主要简单介绍了RAC的信号机制,本篇则以信号为核心,就信号常用的类、操作信号的方法,替换响应处理等方面总结RAC的使用。
目录:
一、RAC中常用的类
二、RAC中常用的宏
三、RAC中信号的常用操作
四、RAC常用的处理事件响应的方法
五、本篇总结
本篇还提供了关于RAC使用的两个测试工程,结合代码学习更加直观:
项目1:
1.测试RAC对信号的各类操作。
2.使用RAC改进一个普通的登录界面。
项目2:
MVVM架构结合RAC响应式编程的开发示例。实现登录界面和的分页数据界面。效果图如下:
一、RAC中常用的类
1.RACSubject
RACSubject是信号RACSignal的一个子类,但它的底部实现与RACSignal有所不同。其订阅信号subscribeNext的方法只是使用nextBlock创建了一个订阅者并保存起来待用,多次调用subscribeNext会保存多个订阅者。只有发送信号sendNext方法执行时,订阅者才会执行nextBlock里的内容,多个订阅者会执行多次。
使用示例:
//1.创建信号
//创建RACSubject不需要block参数
RACSubject *subject = [RACSubject subject];
//2.订阅信号
//这里信号被订阅两次,那么订阅者也创建了两次,保存在RACSubject的subscribers属性数组中。
//那么每当信号有新值发出的时候,每个订阅者都会执行。
[subject subscribeNext:^(id x) {
//block在信号发出新值时调用
NSLog(@"第一个订阅者:%@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"第二个订阅者:%@",x);
}];
//3.发送信号
[subject sendNext:@"6”];
控制台打印:
2018-03-24 13:07:51.425569+0800 ZSTest[2840:124915] 第一个订阅者:6
2018-03-24 13:07:51.426113+0800 ZSTest[2840:124915] 第二个订阅者:6
应用示例:替换代理
我们测试这样一个功能:在当前视图控制器A中点击按钮调转到下一视图控制器B,在B的文本框中输入内容,点击编辑完成按钮回到A,显示B中输入的内容到A的UILabel上。通常我们使用代理来解决这样的问题,那么现在我们可以利用RACSubject的特性来代替常用的代理的功能,其实就跟我们使用block回调一样。具体代码如下:
//1.下一视图控制中添加RACSubject属性。
//SecondViewController.h文件
@interface SecondViewController : BaseViewController
@property (nonatomic, strong) RACSubject *racSubject;
@end
//2.点击编辑完成按钮时,检查代理信号并发送消息,这里传递出一个字典(包含输入的文字)
//SecondViewController.m文件
- (IBAction)completeBtnClick:(id)sender {
if(self.racSubject){
[self.racSubject sendNext:@{@"text":self.txtField.text}];
}
[self.navigationController popViewControllerAnimated:YES];
}
//3.当前视图控制器A的跳转按钮响应方法中,创建secondVC,并为其添加信号属性和订阅信号。
//TestViewCotroller.m文件
- (IBAction)testBtnClick:(id)sender {
SecondViewController *secondVC = [[SecondViewController alloc] initWithNibName:@"SecondViewController" bundle:nil];
//为secondVC设置RACSubject属性,并订阅信号
secondVC.racSubject = [RACSubject subject];
__weak typeof(self) weakSelf = self;
//定阅信号的block会更新文字的显示
[secondVC.racSubject subscribeNext:^(id _Nullable x) {
NSDictionary *infoDic =(NSDictionary *)x;
weakSelf.showLabel.text = infoDic[@"text"];
}];
[self.navigationController pushViewController:secondVC animated:YES];
}
2.RACTuple与RACSequence遍历数组与字典
RACTuple:类似OC的数组,是RAC中用来封装值的元组类,可以配合RACTupleUnpack解元组。
RACSequeue:数组和字典经过rac_sequence方法会被转化为RACSequeue类型,并进一步转为我们常用的信号。订阅此类信号的时候,信号就会被激活并遍历其中的所有值。
使用示例:
//遍历数组
NSArray *characters = @[@"A",@"C",@"B",@"E",@"D"];
[characters.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"char:%@",x);
}];
控制台打印:
char:A
char:C
char:B
char:E
char:D
//遍历字典
NSDictionary *myInfoDic = @{@"name":@"zs",@"nickname":@"FengZi",@"age":@"18"};\
[myInfoDic.rac_sequence.signal subscribeNext:^(id _Nullable x) {
//解元组,注意一一对应
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"myInfoDic:%@-%@",key,value);
}];
控制台打印:
myInfoDic:name-zs
myInfoDic:nickname-FengZi
myInfoDic:age-18
3.RACMulticastConnection:处理重复发送消息的问题
RACMulticastConnection用于解决一个信号被多次订阅后,创建信号中的block被重复调用的问题,所以在实际开发中,使用RACMulticastConnection可以解决网络重复请求的问题。
测试1:普通的信号
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送信号A");
[subscriber sendNext:@"发送信号A"];
return nil;
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第一次订阅:%@",x);
}];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"第二次订阅:%@",x);
}];
控制台打印:
2018-03-28 10:02:00.702607+0800 ZSTest[2446:48444] 发送信号A
2018-03-28 10:02:00.702856+0800 ZSTest[2446:48444] 第一次订阅:发送信号A
2018-03-28 10:02:00.703069+0800 ZSTest[2446:48444] 发送信号A
2018-03-28 10:02:00.703325+0800 ZSTest[2446:48444] 第二次订阅:发送信号A
测试2:使用RACMulticastConnection
//1.创建信号
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送信号B");
[subscriber sendNext:@"发送信号B"];
return nil;
}];
//2.连接信号:publish或者muticast方法
//连接后的信号使用订阅方法时,并不能激活信号,而是将其订阅者保存到数组中。
//在连接对象执行connect方法时,信号中的订阅者会统一调用sendNext方法。
RACMulticastConnection *signalBconnect = [signalB publish];
//3.订阅信号
//使用signalBconnect而不再是signalB
[signalBconnect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"第一次订阅:%@",x);
}];
[signalBconnect.signal subscribeNext:^(id _Nullable x) {
NSLog(@"第二次订阅:%@",x);
}];
//4.连接后激活信号
[signalBconnect connect];
控制台打印:
2018-03-28 10:02:00.704209+0800 ZSTest[2446:48444] 发送信号B
2018-03-28 10:02:00.704368+0800 ZSTest[2446:48444] 第一次订阅:发送信号B
2018-03-28 10:02:00.704543+0800 ZSTest[2446:48444] 第二次订阅:发送信号B
4.RACCommand:用于处理事件的类
RACCommand可以把事件如何处理,如何传递都封装到类中,之后就可以方便的调起它的执行方法。在实际开发中,我们可以用它来封装一个网络操作。
注意:
1.创建方法中block返回一个信号,且不能为nil,但是可以使用[RACSignal empty]表示空信号
2.RACCommand必须被强引用,否则容易被释放
//1.创建RACCommand:initWithSignalBlock
self.command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//我们常在这里创建一个网络请求的信号,也就是封装一个请求数据的操作。
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"网络请求的信号"];
//数据传递完成,必须调用sendComplleted.,否则永远处于执行中。
[subscriber sendCompleted];
return nil;
}];
return signal;
}];
//2.订阅RACCommand中的信号,要等到RACCommand执行后,才能收到消息
[self.command.executionSignals subscribeNext:^(id _Nullable x) {
//这里是一个信号中信号
[x subscribeNext:^(id _Nullable x) {
NSLog(@"收到信号:%@",x);
}];
}];
//改进订阅方法:switchToLatest可以直接获取信号中信号
[self.command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"改进-收到信号:%@",x);
}];
//3.监听RACCommand命令是否执行完毕的信号
//默认会监测一次,所以可以使用skip表示跳过第一次信号。
//这里可以用于App网络请求时,控制加载提示视图的隐藏或者显示
[[self.command.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
if([x boolValue] == YES){
NSLog(@"RACCommand命令正在执行...");
}else{
NSLog(@"RACCommand命令不在执行中!!!")
}
}];
//4.执行RACComand
//方法:- (RACSignal *)execute:(id)input
[self.command execute:@""];
控制台打印:
2018-03-24 14:43:06.571968+0800 ZSTest[3725:171859] RACCommand命令正在执行...
2018-03-24 14:43:06.572526+0800 ZSTest[3725:171859] 收到信号:网络请求的信号
2018-03-24 14:43:06.572662+0800 ZSTest[3725:171859] 改进-收到信号:网络请求的信号
2018-03-24 14:43:06.573506+0800 ZSTest[3725:171859] RACCommand命令不在执行中!!!
二、RAC常用的宏定义
1.RAC(对象,对象属性):绑定属性
输入框背景色绑定了映射后的validUserNameSignal信号,信号变化时背景色更新
RAC(self.userNameTxtField,backgroundColor) = [validUserNameSignal map:^id _Nullable(NSNumber *userNameValid) {
return [userNameValid boolValue] ? [UIColor whiteColor] : [UIColor yellowColor];
}];
2.RACObserve(被观察的对象,被观察对象的属性) :代替KVO监听某个对象的某个属性
[RACObserve(self.view, backgroundColor) subscribeNext:^(id _Nullable x) {
NSLog(@"测试:%@",x);
}];
//颜色变化时将打印
self.view.backgroundColor = [UIColor whiteColor];
self.view.backgroundColor =[UIColor redColor];
3.RACTuplePack与RACTupleUnpack
RACTuplePack:将数据封装成元组
RACTupleUnpack:将元组解包为数据
//使用RACTuplePack封装元组
RACTuple *racTuple = RACTuplePack(@"字符串1",@"字符串2");
NSLog(@"测试racTuple:%@",racTuple);
//使用RACTupleUnpack解元组
RACTupleUnpack(NSString *str1,NSString *str2) = racTuple;
NSLog(@"测试RACTupleUnpack:%@-%@",str1,str2);
控制台打印:
2018-03-26 19:27:27.568399+0800 ZSTest[23113:380213] 测试racTuple:<RACTwoTuple: 0x60400000ed70> (
"\U5b57\U7b26\U4e321",
"\U5b57\U7b26\U4e322"
)
2018-03-26 19:27:27.568623+0800 ZSTest[23113:380213] 测试RACTupleUnpack:字符串1-字符串2
4.@weakify、@strongify
RAC中使用@weakify、@strongify解决Block循环引用的问题。在block内部使用@strongify(self)后就可以使用self操作属性了,但是一定注意这两个宏定义一定要配合使用,可参考源码分析。
@weakify(self);
//RAC处理手势,点击页面,隐藏键盘
[self.tapGesture.rac_gestureSignal subscribeNext:^(__kindof UIGestureRecognizer * _Nullable x) {
@strongify(self);
//经过宏定义处理后就可以使用self了,但此self非彼self。具体可查看源码分析
[self.view endEditing:YES];
}];
三、RAC中关于信号的常用操作
本节整理了以下几种常用信号操作:
1.信号映射:map与flattenMap
2.信号过滤:filter、ignore、 distinctUntilChanged
3.信号合并: combineLatest、reduce、merge、zipWith
4.信号连接:concat、then
5.信号操作时间:timeout、interval、dely
6.信号取值:take、takeLast、takeUntil、
7.信号跳过:skip
8.信号发送顺序:donext、cocompleted
9.获取信号中的信号:switchToLatest
10.信号错误重试:retry
11.信号节流:throttle
12.信号操作多线程:deliverON、subscribeOn
一、信号映射:map与flattenMap
map:将信号内容修改为另一种新值。改变了传递的值
flattenMap:将源信号映射修改为另一种新的信号。修改了信号本身
1.map
将信号文本值修改为文本长度
//block中return的是你希望接收到的值
[[self.txtField.rac_textSignal map:^id _Nullable(NSString * _Nullable value) {
return @(value.length);//必须返回一个对象
}] subscribeNext:^(id _Nullable x) {
//输入abcd,打印了输入字符的长度
NSLog(@"打印x:%@",x);
}];
控制台打印:
2018-03-23 10:52:41.831785+0800 ZSTest[1143:44274] 打印x:1
2018-03-23 10:52:42.575238+0800 ZSTest[1143:44274] 打印x:2
2018-03-23 10:52:43.602008+0800 ZSTest[1143:44274] 打印x:3
2018-03-23 10:52:44.054940+0800 ZSTest[1143:44274] 打印x:4
2.flattenMap
flattenMap的block返回的是你想要的信号
//创建一个普通信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"发送信号:1"];
[subscriber sendCompleted];
return nil;
}];
//创建一个发送信号的信号,信号的信号
RACSignal *signalOfSignals = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:signal];
[subscriber sendCompleted];
return nil;
}];
//订阅信号中的信号
[signalOfSignals subscribeNext:^(id _Nullable x) {
//不使用flattenMap,会打印出内部信号
NSLog(@"订阅signalOfSignals:%@",x);
}];
[[signalOfSignals flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
return value;
}] subscribeNext:^(id _Nullable x) {
//使用flattenMap,会打印内部信号的值
NSLog(@"使用flattenMap后订阅signalOfSignals:%@",x);
}];
控制台打印:
2018-03-23 11:23:42.920455+0800 ZSTest[1363:61658] 订阅signalOfSignals:<RACDynamicSignal: 0x60400023a320> name:
2018-03-23 11:23:42.920791+0800 ZSTest[1363:61658] 使用flattenMap后订阅signalOfSignals:发送信号:1
特别说明:信号中信号常出现在我们封装一个网络请求为信号的时候,这时候注意flattenMap的使用。
二、信号过滤:filter、ignore、 distinctUntilChanged
1.filter
过滤信号,符合条件的信号才能发出消息。
示例:输入1234,当输入到4(文本长度大于3)的时候才开始打印如下的信息
[[self.txtField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
return value.length > 3;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"打印x:%@",x);
}];
控制台打印:
2018-03-23 11:39:23.371432+0800 ZSTest[1428:68939] 打印x:1234
2.ignore
忽略信号,针对信号值的某一种状态进行忽略,忽略时不会发送消息。
示例:监听每次的输入,但是当文本框内的内容是"a"时不会打印
[[self.txtField.rac_textSignal ignore:@"a"] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"ignore测试打印:%@",x);
}];
3.distinctUntilChanged
当上次的值与当前值有变化时才会发出消息,否则信息被忽略
//为了方便测试,我们监测控制器的currentText属性来修改Label的文本值。
__weak typeof(self)weakSelf = self;
[[RACObserve(self, currentText) distinctUntilChanged] subscribeNext:^(id _Nullable x) {
NSLog(@"使用%@更新testLabel的值",x);
weakSelf.testLabel.text = x;
}];
//currentTxt未被赋初值,所以第一次打印null,我们自己修改三次值,只打印两次
self.currentText = @"hello";
self.currentText = @"world";
self.currentText = @"world";
控制台打印:
2018-03-23 16:43:54.617385+0800 ZSTest[3598:220992] 使用(null)更新testLabel的值
2018-03-23 16:43:54.618026+0800 ZSTest[3598:220992] 使用hello更新testLabel的值
2018-03-23 16:43:54.618380+0800 ZSTest[3598:220992] 使用world更新testLabel的值
三、信号合并:combineLatest、reduce、merge、zipWith
为了便于测试,这里先创建两个RACSubject类型的信号用于测试,此类信号只有发送信号sendNext方法执行时,订阅者才会执行nextBlock里的内容;
RACSubject *signalOne = [RACSubject subject];
[signalOne subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号one:%@",x);
}];
RACSubject *signalTwo = [RACSubject subject];
[signalTwo subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号Two:%@",x);
}];
1. combineLatest:合并信号
合并信号的效果就是,这多个信号都至少有过一次订阅信号sendNext的操作,才会触发合并的信号。下面的测试如果只有signalOne执行sendNext方法,那么combineLatest后的信号不会被触发。
[[RACSignal combineLatest:@[signalOne,signalTwo]] subscribeNext:^(RACTuple * _Nullable x) {
//解元组:合并信号得到的是一个元组,里面存放的是两个信号发送的消息
RACTupleUnpack(NSString *str1,NSString *str2) = x;
NSLog(@"combineLatest:str1-%@,str2-%@",str1,str2);
}];
[signalOne sendNext:@"1"];
[signalTwo sendNext:@"2”];
控制台打印:
2018-03-23 14:29:53.198724+0800 ZSTest[2172:143774] 订阅信号one:1
2018-03-23 14:29:53.199673+0800 ZSTest[2172:143774] 订阅信号Two:2
2018-03-23 14:29:53.200075+0800 ZSTest[2172:143774] combineLatest:<RACTuple: 0x60000000d9a0> (
1,
2
)
2. reduce:聚合信号
combineLatest合并后的信号订阅后,得到的是一个元组(包含每个被合并信号的新值)。然而在开发中,我们往往需要检测多个信号合并后的效果(比如用户名和密码信号有效时,登录按钮才可以点击),这里就用到了reduce来实现信号聚合。
reduce聚合操作中的block参数个数随合并信号的数量而定,有多少个信号被合并,blcok中的参数就有多少个。这些参数一一对应被合并的信号,是它们对应的新值。
[[RACSignal combineLatest:@[signalOne,signalTwo] reduce:^id(NSString *strOne,NSString *strTwo){
return [NSString stringWithFormat:@"%@-%@",strOne,strTwo];
}] subscribeNext:^(id _Nullable x) {
NSLog(@"combineLatest-reduce:%@",x);
}];
[signalOne sendNext:@"1"];
[signalTwo sendNext:@"2"];
控制台打印:
2018-03-23 14:40:08.977580+0800 ZSTest[2288:149816] 订阅信号one:1
2018-03-23 14:40:08.978566+0800 ZSTest[2288:149816] 订阅信号Two:2
2018-03-23 14:40:08.979587+0800 ZSTest[2288:149816] combineLatest-reduce:1-2
3. merge:合并信号
当合并后的信号被订阅时,就会订阅里面所有的信号
测试1:将多个信号合并之后,当其中任何一个信号发送消息时,都能被监测到。
RACSignal *mergeSignal = [signalOne merge:signalTwo];
[mergeSignal subscribeNext:^(id _Nullable x) {
NSLog(@"mergeSignal:%@",x);
}];
//只调用其中一个信号,就会触发merge合并的信号
[signalOne sendNext:@"测试信号1"];
控制台打印:
2018-03-23 14:53:34.342899+0800 ZSTest[2577:160009] 订阅信号one:测试信号1
2018-03-23 14:53:34.343124+0800 ZSTest[2577:160009] mergeSignal:测试信号1
测试2:当合并后的信号被订阅时,就会订阅里面所有的信号
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signal1"];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"signal2"];
return nil;
}];
RACSignal *mergeSignals = [signal1 merge:signal2];
[mergeSignals subscribeNext:^(id x) {
NSLog(@"mergeSignals:%@",x);
}];
控制台打印:
2018-03-23 18:10:29.623099+0800 ZSTest[4444:270816] mergeSignals:signal1
2018-03-23 18:10:29.623721+0800 ZSTest[4444:270816] mergeSignals:signal2
4. zipWith:压缩信号
1.zipWith把两个信号压缩成为一个信号。
2.只有当两个信号同时发出信号时,两个信号的内容才会被合并为一个元组,触发压缩流的next事件。比如:当一个界面多个请求的时候,要等所有请求完成才更新UI。元组内元素顺序只与压缩信号的顺序有关,与发送信号的顺序无关。
RACSignal *zipSignal = [signalOne zipWith:signalTwo];
[zipSignal subscribeNext:^(id _Nullable x) {
//解元组:合并信号得到的是一个元组,里面存放的是两个信号发送的消息
RACTupleUnpack(NSString *str1,NSString *str2) = x;
NSLog(@"zipSignal:str1-%@,str2-%@",str1,str2);
}];
[signalOne sendNext:@"测试zipSignalMsgOne"];
[signalTwo sendNext:@"测试zipSignalMsgTwo"];
控制台打印:
2018-03-23 15:23:58.989780+0800 ZSTest[2926:177798] 订阅信号one:测试zipSignalMsgOne
2018-03-23 15:23:58.990012+0800 ZSTest[2926:177798] 订阅信号Two:测试zipSignalMsgTwo
2018-03-23 15:23:58.991056+0800 ZSTest[2926:177798] zipSignal:str1-测试zipSignalMsgOne,str2-测试zipSignalMsgTwo
四、信号拼接:concat、then
1.concat
1.使用concat可以按序拼接多个信号,拼接后的信号按序执行。
2.使用concat连接信号后,每个信号无需再单独订阅,其内部会按序自动订阅
3.前面的信号必须执行sendCompleted,后面的信号才会被激活
RACSignal *signalOne = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalOne"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalTwo = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalTwo"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalThree = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"signalThree"];
[subscriber sendCompleted];
return nil;
}];
//拼接了三个信号,订阅之后,三个信号依次激活
RACSignal *concatSignal = [[signalOne concat:signalThree] concat:signalTwo];
[concatSignal subscribeNext:^(id _Nullable x) {
NSLog(@"信号被激活:%@",x);
}];
控制台打印:
2018-03-22 17:36:47.565105+0800 ZSTest[6018:274201] 信号被激活:signalOne
2018-03-22 17:36:47.565403+0800 ZSTest[6018:274201] 信号被激活:signalThree
2018-03-22 17:36:47.565609+0800 ZSTest[6018:274201] 信号被激活:signalTwo
2.then:连接信号
使用then连接信号,上一个信号完成后,才会连接then返回的信号,所以then连接的上一个信号必须使用sendCompleted,否则后续信号无法执行。
then连接的多个信号与concat不同的是:之前的信号会被忽略掉,即订阅信号只会接收到最后一个信号的值
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"信号1");
[subscriber sendNext:@"发送信号1"];
[subscriber sendCompleted];
return nil;
}] then:^RACSignal *{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"信号2");
[subscriber sendNext:@"发送信号2"];
[subscriber sendCompleted];
return nil;
}];
}]then:^RACSignal * _Nonnull{
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"信号3");
[subscriber sendNext:@"发送信号3"];
[subscriber sendCompleted];
return nil;
}];
}] subscribeNext:^(id x) {
//只能接收到最后一个信号的值
NSLog(@"订阅信号:%@",x);
}];
控制台打印:
2018-03-23 16:53:52.819003+0800 ZSTest[3668:227466] 信号1
2018-03-23 16:53:52.819762+0800 ZSTest[3668:227466] 信号2
2018-03-23 16:53:52.820008+0800 ZSTest[3668:227466] 信号3
2018-03-23 16:53:52.820139+0800 ZSTest[3668:227466] 订阅信号:发送信号3
五、信号操作时间:timeout、interval、dely
1. interval
创建定时器信号,每固定时间发送一次信号
RACSignal *intervalSignal = [RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]];
//只知道使用take结束定时器这一种方法,不知道还有没有其他方法
[[intervalSignal take:5]subscribeNext:^(id _Nullable x) {
//订阅定时器信号,启动定时器,只打印5次
NSLog(@"interval,定时器打印");
}];
2.timeout
可以设置超时操作,让一个信号在规定时间之后自动报错
创建信号时不能使用sendCompleted,因为这样的话一旦发送了消息就取消订阅了。
RACSignal *timeOutSignal = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"timeOutSignal发送信号"];
//[subscriber sendCompleted];
return nil;
}] timeout:5 onScheduler:[RACScheduler currentScheduler]];
[timeOutSignal subscribeNext:^(id _Nullable x) {
NSLog(@"timeOutSignal:%@",x);
} error:^(NSError * _Nullable error) {
//5秒后执行打印:
//timeOutSignal:出现Error-Error Domain=RACSignalErrorDomain Code=1 "(null)"
NSLog(@"timeOutSignal:出现Error-%@",error);
} completed:^{
NSLog(@"timeOutSignal:complete");
}];
3.delay
延迟发送sendNext
RACSignal *delaySignal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"delaySignal-sendNext"];
return nil;
}];
//10秒后才收到消息,执行打印
[[delaySignal delay:10] subscribeNext:^(id _Nullable x) {
NSLog(@"delaySignal:%@",x);
}];
六、信号取值take、takeLast、takeUntil
首先创建一个signal来测试这三个方法:
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"发送消息1"];
[subscriber sendNext:@"发送消息2"];
[subscriber sendNext:@"发送消息3"];
[subscriber sendNext:@"发送消息4"];
[subscriber sendCompleted];
return nil;
}];
1.take:从开始共取N次的next值
[[signal take:2] subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号:%@",x);
}];
控制台打印:
2018-03-23 17:15:09.865290+0800 ZSTest[3835:237999] 订阅信号:发送消息1
2018-03-23 17:15:09.865617+0800 ZSTest[3835:237999] 订阅信号:发送消息2
2.takeLast:从最后共取值N次next的值
[[signal takeLast:3]subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号:%@",x);
}];
控制台打印:
2018-03-23 17:19:51.343909+0800 ZSTest[3933:241042] 订阅信号:发送消息2
2018-03-23 17:19:51.344063+0800 ZSTest[3933:241042] 订阅信号:发送消息3
2018-03-23 17:19:51.344177+0800 ZSTest[3933:241042] 订阅信号:发送消息4
3.takeUntil:(RACSignal *)
使用RACSubject类型的信号来测试,直到某个信号执行完成 ,才获取信号
RACSubject *signalA = [RACSubject subject];
[signalA subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号A:%@",x);
}];
__weak typeof(self)weakSelf = self;
//[RACObserve(self, currentText)发送消息知道signalA信号结束
[[RACObserve(self, currentText) takeUntil:signalA] subscribeNext:^(id _Nullable x) {
NSLog(@"使用%@更新testLabel的值",x);
weakSelf.testLabel.text = x;
}];
self.currentText = @"0";
self.currentText = @"1";
self.currentText = @"2";
[signalA sendCompleted];//信号A结束之后,监听testLabel文本的信号也不在发送消息了
self.currentText = @"3";
NSLog(@"代码执行到此行。。。。");
控制台打印:
2018-03-23 17:31:08.907925+0800 ZSTest[4044:247412] 使用(null)更新testLabel的值
2018-03-23 17:31:08.908531+0800 ZSTest[4044:247412] 使用0更新testLabel的值
2018-03-23 17:31:08.908957+0800 ZSTest[4044:247412] 使用1更新testLabel的值
2018-03-23 17:31:08.909194+0800 ZSTest[4044:247412] 使用2更新testLabel的值
2018-03-23 17:31:08.909725+0800 ZSTest[4044:247412] 代码执行到此行。。。。
七、信号跳过:skip
使用skip跳过几个信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"第一次发送消息"];
[subscriber sendNext:@"第二次发送消息"];
[subscriber sendNext:@"第三次发送消息"];
[subscriber sendNext:@"第四次发送消息"];
return nil;
}];
[[signal skip:2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
控制台打印:
2018-03-23 17:48:06.479104+0800 ZSTest[4284:259095] 第三次发送消息
2018-03-23 17:48:06.479376+0800 ZSTest[4284:259095] 第四次发送消息
八、信号发送顺序:doNext、doCompleted
发送信号前与发送信号后操作:doNext、doCompleted
doNext:在订阅者发送消息sendNext之前执行
doCompleted:在订阅者发送完成sendCompleted之后执行
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"发送信号:1"];
[subscriber sendCompleted];
return nil;
}];
[[[signal doNext:^(id _Nullable x) {
NSLog(@"执行doNext");
}] doCompleted:^{
NSLog(@"执行doComplete");
}] subscribeNext:^(id _Nullable x) {
NSLog(@"订阅信号:%@",x);
}];
控制台打印:
2018-03-28 11:20:42.881535+0800 ZSTest[2656:81106] 执行doNext
2018-03-28 11:20:42.881841+0800 ZSTest[2656:81106] 订阅信号:发送信号:1
2018-03-28 11:20:42.882583+0800 ZSTest[2656:81106] 执行doComplete
九、获取信号中的信号:switchToLatest
switchToLatest只能用于信号中的信号(否则崩溃),获取最新发送的信号。
//创建一个普通信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"发送信号:1"];
[subscriber sendCompleted];
return nil;
}];
//创建一个发送信号的信号,信号的信号
RACSignal *signalOfSignals = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:signal];
[subscriber sendCompleted];
return nil;
}];
//订阅最近发出的信号
[signalOfSignals.switchToLatest subscribeNext:^(id _Nullable x) {
//控制台打印:switchToLatest打印:发送信号:1
NSLog(@"switchToLatest打印:%@",x);
}];
特别说明:
可以看出switchToLatest和flattenMap的功能很相似,但是它们有一主要区别:
十、信号错误重试:retry
retry:只要失败就重新执行信号
static int signalANum = 0;
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
if (signalANum == 5) {
[subscriber sendNext:@"signalANum is 5"];
[subscriber sendCompleted];
}else{
NSLog(@"signalANum错误!!!");
[subscriber sendError:nil];
}
signalANum++;
return nil;
}];
[[signalA retry] subscribeNext:^(id _Nullable x) {
NSLog(@"StringA-Next:%@",x);
} error:^(NSError * _Nullable error) {
//特别注意:这里并没有打印
NSLog(@"signalA-Errror");
}] ;
控制台打印:
2018-03-24 09:46:38.996888+0800 ZSTest[818:14322] signalANum错误!!!
2018-03-24 09:46:39.063427+0800 ZSTest[818:14322] signalANum错误!!!
2018-03-24 09:46:39.064203+0800 ZSTest[818:14322] signalANum错误!!!
2018-03-24 09:46:39.064567+0800 ZSTest[818:14322] signalANum错误!!!
2018-03-24 09:46:39.064819+0800 ZSTest[818:14322] signalANum错误!!!
2018-03-24 09:46:39.065199+0800 ZSTest[818:14322] StringA-Next:signalANum is 5
十一、信号节流:throttle
当某个信号发送比较频繁时,可以使用throttle节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"发送消息11"];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"发送消息21"];
[subscriber sendNext:@"发送消息22"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"发送消息31"];
[subscriber sendNext:@"发送消息32"];
[subscriber sendNext:@"发送消息33"];
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"发送消息41"];
[subscriber sendNext:@"发送消息42"];
[subscriber sendNext:@"发送消息43"];
[subscriber sendNext:@"发送消息44"];
});
return nil;
}] throttle:2] subscribeNext:^(id _Nullable x) {
NSLog(@"Next:%@",x);
}];
控制台打印:
2018-03-24 11:05:48.411464+0800 ZSTest[2063:62521] Next:发送消息11
2018-03-24 11:05:52.426162+0800 ZSTest[2063:62521] Next:发送消息44
十二、信号关于线程的操作
副作用:关于信号与线程,我们把在创建信号时block中的代码称之为副作用。
deliverON:切换到指定线程中,可用于回到主线中刷新UI,内容传递切换到指定线程中,
subscribeOn:内容传递和副作用都会切换到指定线程中。
deliverOnMainThread:能保证原信号subscribeNext,sendError,sendCompleted都在主线程MainThread中执行。
//测试1:系统并行队列中异步执行,未使用deliverON切换线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"测试1-endNext"];
NSLog(@"测试1-当前线程:%@",[NSThread currentThread]);
return nil;
}] subscribeNext:^(id _Nullable x) {
NSLog(@"测试1-Next:%@",x);
NSLog(@"测试1-Next当前线程:%@",[NSThread currentThread]);
}];
}) ;
//测试2:系统并行队列中异步执行,使用deliverON切换线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"测试2-endNext"];
NSLog(@"测试2-当前线程:%@",[NSThread currentThread]);
return nil;
}] deliverOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id _Nullable x) {
NSLog(@"测试2-Next:%@",x);
NSLog(@"测试2-Next当前线程:%@",[NSThread currentThread]);
}];
}) ;
//测试3:系统并行队列中异步执行,使用subscribeOn切换线程
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"测试3-sendNext"];
NSLog(@"测试3-sendNext当前线程:%@",[NSThread currentThread]);
return nil;
}] subscribeOn:[RACScheduler mainThreadScheduler]] subscribeNext:^(id _Nullable x) {
NSLog(@"测试3-Next:%@",x);
NSLog(@"测试3-Next当前线程:%@",[NSThread currentThread]);
}];
}) ;
控制台打印:
2018-03-24 12:37:48.025872+0800 ZSTest[2624:109537] 测试1-Next:测试1-endNext
2018-03-24 12:37:48.026152+0800 ZSTest[2624:109537] 测试1-Next当前线程:<NSThread: 0x60400046e640>{number = 5, name = (null)}
2018-03-24 12:37:48.026746+0800 ZSTest[2624:109537] 测试1-sendNext当前线程:<NSThread: 0x60400046e640>{number = 5, name = (null)}
2018-03-24 12:37:48.027784+0800 ZSTest[2624:109537] 测试2-sendNext当前线程:<NSThread: 0x60400046e640>{number = 5, name = (null)}
2018-03-24 12:37:48.073294+0800 ZSTest[2624:109360] 测试2-Next:测试2-sendNext
2018-03-24 12:37:48.073890+0800 ZSTest[2624:109360] 测试2-Next当前线程:<NSThread: 0x60400006a280>{number = 1, name = main}
2018-03-24 12:37:48.074181+0800 ZSTest[2624:109360] 测试3-Next:测试3-sendNext
2018-03-24 12:37:48.074511+0800 ZSTest[2624:109360] 测试3-Next当前线程:<NSThread: 0x60400006a280>{number = 1, name = main}
2018-03-24 12:37:48.074646+0800 ZSTest[2624:109360] 测试3-sendNext当前线程:<NSThread: 0x60400006a280>{number = 1, name = main}
分析:
测试1:未切换线程,发送消息与接收消息都在异步线程中
测试2:使用deliverON,发送消息还在原来的线程,但是接收消息切换到主线程。
测试2:使用subscribeON,发送消息和接收消息都被切换到了主线程中执行。
四、RAC常用的处理事件响应的方法
1.代替代理的使用
基础篇里已经有一种使用RACSubject替换代理的方法,这里是另一种形式的替换。在视图控制中添加自定义视图CustomView,其上有一按钮testBtn添加了响应方法testBtnClick:。此时可以使用RAC在不使用代理的情况下,在视图控制中监听自定义视图中按钮的点击:
关键方法:rac_signalForSelector
使用说明:
1.通过rac_signalForSelector方法,以按钮响应方法为参数,得到一个信号。
2.订阅信号,在按钮点击时会发出信号。经过测试,即使testBtnClick方法没有在自定义视图的.h文件中声明,执行也是正常的。
[[_customView rac_signalForSelector:@selector(testBtnClick:)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"testBtn点击了。。。");
}];
2.代替按钮等控制视图的响应事件
创建一个类似按钮的响应控件,我们可以不必再为其添加响应方法。使用RAC可以将按钮点击事件转化为信号,点击按钮会发送信号,执行订阅方法。
关键方法:rac_signalForControlEvents
[[testBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"testBtn点击了。。。");
}];
3.代替KVO,监听对象属性变化
关键方法:rac_valuesAndChangesForKeyPath
使用说明:
1.自定义视图_customView属性frame的变化被转化信号,frame发生变化的时候,会发送信号。
2.observer可以为nil,但是会报警告。
[[_customView rac_valuesAndChangesForKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer: nil] subscribeNext:^(id x) {
NSLog(@"CustomView的Frame值变化了:%@",x);
}];
_customView.frame = CGRectZero;
4.监听文本输入变化
关键方法:rac_textSignal
UITextField与UITextView输入视图内容的变化,我们也可以采用RAC的方法来监听
[[_testTxtView rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"当前文本输入内容:%@",x);
}];
5.代替通知的使用
关键方法:rac_addObserverForName
步骤1: 添加通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:@"SecondVCNotificaitonName" object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSDictionary *objectDic = (NSDictionary *)x.object;
NSLog(@"获取到通知里的文本:%@",objectDic[@"text"]);
}];
步骤2:发起通知
//SecondVC中点击按钮,将输入框中的文本封装成字典,将其作为参数发起通知
- (IBAction)completeBtnClick:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:@"SecondVCNotificaitonName" object:@{@"text":self.txtField.text}];
}
6.多请求汇总处理
关键方法:rac_liftSelector:withSignals:
//下载任务1
RACSignal *downLoad1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@{@"data2":@"value1"}];
[subscriber sendCompleted];
return nil;
}];
//下载任务2
RACSignal *downLoad2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@{@"data2":@"value2"}];
[subscriber sendCompleted];
return nil;
}];
//多信号对应多参数,注意顺序与格式
[self rac_liftSelector:@selector(handleAllTasksWithT1:withT2:) withSignals:downLoad1,downLoad2, nil];
//集中处理所有的请求
- (void)handleAllTasksWithT1:(id)data1 withT2:(id)data2{
NSLog(@"下载任务全部完成:%@,%@",data1,data2);
}
五、本篇总结
写到这里,其实RAC还是有好多东西没有在这里涉及,本篇也只是对于它最常用的部分进行了归纳总结,尤其是我对于RAC在MVVM架构中的使用还不太熟练。RAC的学习,这仅仅是一个开始,继续努力吧!
参考链接:
1.RAC核心元素与信号流
2.iOS常用API整理
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=27g5vjk6xykq