ReactiveCocoa函数响应式编程-应用篇

使用RAC其实就是一个创建信号订阅信号的过程。上篇ReactiveCocoa函数响应式编程-基础篇,主要简单介绍了RAC的信号机制,本篇则以信号为核心,就信号常用的类、操作信号的方法,替换响应处理等方面总结RAC的使用。

目录:

一、RAC中常用的类
二、RAC中常用的宏
三、RAC中信号的常用操作
四、RAC常用的处理事件响应的方法
五、本篇总结

本篇还提供了关于RAC使用的两个测试工程,结合代码学习更加直观
项目1
1.测试RAC对信号的各类操作。
2.使用RAC改进一个普通的登录界面。
项目2
MVVM架构结合RAC响应式编程的开发示例。实现登录界面和的分页数据界面。效果图如下:

RAC&&MVVM实现一个登录界面和一个分页数据表视图界面

一、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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
禁止转载,如需转载请通过简信或评论联系作者。
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350