一、概述
ReactiveCocoa简称RAC,基于响应式编程思想的Objective-C实践。本文主要结合代码示例列举了RAC的常用类,常用方法和常用宏,帮助开发者快速上手怎样使用RAC。
二、常用的类
1、RACSiganl:信号类,用于传递改变的数据,可传递以下三种状态:
- sendNext:可理解为传递正确数据,告诉订阅者进行下一步处理
- sendError:传递的数据错误,告诉订阅者错误处理
- sendCompleted:告诉订阅者已完成
2、RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据。
-
没有订阅者的 RACSiganl 就是冷信号,相反有订阅者的 RACSiganl 就是热信号。为了更好地理解,请参考下图:
下面举个RAC简单用法的栗子:
//信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
[subscriber sendNext:@"666666"]; //发送正确数据
// [subscriber sendError:[NSError errorWithDomain:@"xxx" code:1001 userInfo:@{@"errorMessage" : @"错误"}]]; //发送错误
// [subscriber sendCompleted]; //完成
return nil;
}];
//订阅者
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到数据
} error:^(NSError * _Nullable error) {
NSLog(@"--%@--",error); //出错处理
} completed:^{
NSLog(@"完成"); // 信号完成
}];
createSignal 方法block中对应的就是上面说的 RACSiganl 传递的三种状态(sendNext, sendError, sendCompleted),subscribeNext就是订阅方法,对应着 RACSiganl 发出的三种状态做出不同的处理。
3、RACSubject:既可以充当信号,也可以发送信号
其实用法和上面栗子类似:
RACSubject *subj = [RACSubject subject];
//订阅信号
[subj subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到数据
} error:^(NSError * _Nullable error) {
NSLog(@"--%@--",error); //出错处理
} completed:^{
NSLog(@"完成"); // 信号完成
}];
//发送信号
[subj sendNext:@"666666"]; //发送正确数据
// [subscriber sendError:[NSError errorWithDomain:@"xxx" code:1001 userInfo:@{@"errorMessage" : @"错误"}]]; //发送错误
// [subscriber sendCompleted]; //完成
RACSubject的作用通常用来代替代理。下面上代码:
- 首先创建一个RedView的类继承于UIView
RedView.h
#import
#import
@interface RedView : UIView
@property (nonatomic,strong) RACSubject *subject;
@end
RedView.m
#import "RedView.h"
@implementation RedView
- (RACSubject *)subject {
if (_subject == nil) {
_subject = [RACSubject subject];
}
return _subject;
}
- (IBAction)btnClick:(UIButton *)sender {
[self.subject sendNext:@"777777"];
}
@end
控制器中代码:
#import "OrderController.h"
#import
#import "RedView.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[self.redView.subject subscribeNext:^(id _Nullable x) {
NSLog(@"--%@--",x); //接收到数据
}];
}
这样就完成了和代理一样的功能
4、RACMulticastConnection:信号连接类
一般情况下,信号被订阅多少次,信号创建是的block就调用多少次。但是实际开发中,block只需调用一次就可以了。RACMulticastConnection可以实现不管订阅多少次信号,信号的block只请求一次:
- (void)viewDidLoad {
[super viewDidLoad];
//不管订阅多少次信号,只请求一次
//1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
NSLog(@"请求数据");
[subscriber sendNext:@"请求数据:777"];
return nil;
}];
//2.把信号转换成连接类
RACMulticastConnection * connection = [signal publish];
//3.订阅连接类信号
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅1:%@",x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅2:%@",x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"订阅3:%@",x);
}];
//4.连接
[connection connect];
}
上面的例子中信号被订阅了3次,但是请求数据只请求了1次。
5、RACCommand:处理事件的类
- (void)viewDidLoad {
[super viewDidLoad];
///1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
/*******此block执行命令时调用*******/
//input:对应执行命令的参数,拿到参数后可以写处理事件代码
NSLog(@"input:%@",input);
//不能返回空的信号
return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//传递事件产生的数据
[subscriber sendNext:@"执行命令产生的数据"];
return nil;
}];
}];
///2.订阅命令内部的信号
//方式1:
//1.执行命令
RACSignal *signal = [command execute:@"666"];
//2.订阅信号
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"~%@~",x);
}];
//方式2:(注意:必须在执行命令前订阅信号)
//1.订阅信号
//executionSignals:信号源,信号中的信号
[command.executionSignals subscribeNext:^(id _Nullable x) {
[x subscribeNext:^(id _Nullable x) {
NSLog(@"~%@~",x);
}];
}];
//2.执行命令
[command execute:@"777"];
///3.监听事件有没有完成
[command.executing subscribeNext:^(NSNumber * _Nullable x) {
if ([x boolValue] == YES) {
NSLog(@"正在执行");
}else {
NSLog(@"执行完成/没有执行");
}
}];
}
上面的例子例举了创建command对象block中,事件如何处理,事件产生的数据如何传递。command.executing监控事件执行的过程。
三、常用的方法
- 监听某个对象是否调用某个方法:
#import "OrderController.h"
#import
#import "RedView.h"
#import "HomeController.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[[self rac_signalForSelector:@selector(push:)] subscribeNext:^(RACTuple * _Nullable x) {
NSLog(@"跳转");
}];
}
- (IBAction)push:(id)sender {
HomeController *vc = [[HomeController alloc] init];
[self.navigationController pushViewController:vc animated:YES];
}
当点击按钮调用 push: 方法时就会监听到 push: 方法被调用了,从而打印出 “跳转”
- 代替KVO:
#import "OrderController.h"
#import
#import "RedView.h"
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[[_redView rac_valuesForKeyPath:@"frame" observer:self] subscribeNext:^(id _Nullable x) {
//修改的值
NSLog(@"%@",x);
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_redView.frame = CGRectMake(50, 50, 200, 200);
}
上述例子同KVO功能一样,监听了_redView对象对象中的frame属性,当点击屏幕调用touchesBegan改变_redView的frame时,block里的方法会被调用。
- 监听事件
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"按钮被点击了");
}];
给按钮添加点击事件
- 代替通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
NSLog(@"键盘被弹出");
}];
实现监听键盘弹出通知
- 监听文本框变化
[[self.textField rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"UITextField:%@",x);
}];
- 所有信号都返回结果的时候,统一做处理
- (void)viewDidLoad {
[super viewDidLoad];
//信号一
RACSignal *sg1 = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//模拟从服务器请求数据
[subscriber sendNext:@"请求的数据1"]; //请求成功后发送数据
return nil;
}];
//信号二
RACSignal *sg2 = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//模拟从服务器请求数据
[subscriber sendNext:@"请求的数据2"]; //请求成功后发送数据
return nil;
}];
//方法upDateUI的参数,对应每个信号发送的数据
[self rac_liftSelector:@selector(upDateUIWithData1:data2:) withSignalsFromArray:@[sg1,sg2]];
}
- (void)upDateUIWithData1:(NSString *)data1 data2:(NSString *)data2{
//拿到请求数据
NSLog(@"更新UI:%@--%@",data1,data2);
}
四、常用的宏
- RAC(xx,xx):给某个对象的某个属性绑定信号,只要产生信号内容,就会把内容给属性赋值
@interface OrderController ()
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UILabel *label;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
//方法一,普通方法实现
@weakify(self)
[_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self)
self.label.text = x;
}];
//方法二,宏方法实现
RAC(_label,text) = _textField.rac_textSignal;
}
上面例子中方法二是利用宏实现的,首先将_label的text属性绑定了_textField的信号,只要_textField内容发生改变,就会赋值给_label的text属性。 例子中两种方法实现的效果一样,用RAC()宏写更简便。
- RACObserve(_redView, frame):只要对象属性改变就会产生信号
@interface OrderController ()
@property (weak, nonatomic) IBOutlet RedView *redView;
@end
@implementation OrderController
- (void)viewDidLoad {
[super viewDidLoad];
[RACObserve(_redView, frame) subscribeNext:^(id _Nullable x) {
//修改的致
NSLog(@"%@",x);
}];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
_redView.frame = CGRectMake(50, 50, 200, 200);
}
与上面说的代 替KVO: 举的例子实现效果一样
- @weakify(self),@strongify(self):解决RAC中的循环引用
@weakify(self)
[_textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
@strongify(self)
self.label.text = x;
}];
在block外加@weakify(self)方法,block里面加 @strongify(self)方法,之后只用self.就不会产生循环引用。
五、RAC高级操作方法
- bind
具体操作:给RAC的信号进行绑定,只要信号已发送数据,就能监听到,从而把数据改成自己想要的
- (void)viewDidLoad {
[super viewDidLoad];
//1.创建信号
RACSubject *subj = [RACSubject subject];
//2.绑定信号
RACSignal *bindSignal = [subj bind:^RACSignalBindBlock _Nonnull{
return ^RACSignal *(id value, BOOL *stop) {
//只要源信号发送数据,就会调用此block
//此block作用:处理源信号内容
//value:信号源发送内容
value = [NSString stringWithFormat:@"orange%@",value];
NSLog(@"修改后的value:%@",value);
return [RACReturnSignal return:value];
};
}];
//3.订阅绑定信号
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到绑定信号处理完的信号:%@",x);
}];
//4.发送数据
[subj sendNext:@"888888"];
}
上述例子中,发送的信号为“888888”,通过绑定信号,对源信号数据进行修改,订阅后的信号内容就被修改了。
- 映射
映射可以把源信号内容更改成新的内容
- (void)viewDidLoad {
[super viewDidLoad];
//创建信号
RACSubject *subj = [RACSubject subject];
//绑定信号
RACSignal *bindSignalX = [subj map:^id _Nullable(id _Nullable value) {
//返回的类型就是你需要映射的值
return [NSString stringWithFormat:@"%d",[value intValue] + 1];
}];
//订阅信号
[bindSignalX subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//发送数据
[subj sendNext:@"666666"];
}
上述例子中,绑定信号block中,将value值+1,直接return的值就是要映射的值,所以在订阅信号中的内容就在发送数据基础上+1。
- 压缩信号:所有信号都发送内容时,才会调用
使用场景:当一个界面多个请求时,要等所有请求完成更新UI
- (void)viewDidLoad {
[super viewDidLoad];
RACSubject *s1 = [RACSubject subject];
RACSubject *s2 = [RACSubject subject];
//压缩成一个信号
RACSignal *yaS = [s1 zipWith:s2];
//订阅信号
[yaS subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//发送信号
[s1 sendNext:@"1"];
[s2 sendNext:@"2"];
}
上面例子中,当s1和s2同时发送信号时,订阅信号的block才会调用。
- 组合信号:将两个信号组合,并且可以同时处理两个信号的内容
使用场景:登录页面账号输入框和密码输入框同时输入内容时,登录按钮才能点击
@interface HomeController ()
@property (weak, nonatomic) IBOutlet UIButton *btn;
@property (weak, nonatomic) IBOutlet UITextField *textField;
@property (weak, nonatomic) IBOutlet UITextField *textField1;
@end
@implementation HomeController
- (void)viewDidLoad {
[super viewDidLoad];
@weakify(self)
//reduce:聚合的意思,合并两个信号数据,进行汇总计算时使用的
RACSignal *signal = [RACSignal combineLatest:@[self.textField.rac_textSignal, self.textField1.rac_textSignal] reduce:^id (NSString *text, NSString *text1){
//判断输入框位数(需转成NSNumber类型)
BOOL isHidden;
if (text.length >= 3 && text1.length >= 6) {
isHidden = YES;
}else {
isHidden = NO;
}
return @(isHidden);
}];
//订阅信号
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"达成条件返回结果:%@",x);
@strongify(self)
self.btn.hidden = ![x boolValue];
}];
}
上面例子中,combineLatest:方法组合了textField和textField1的信号,reduce的block中是对两个组合信号的数据进行计算并返回计算后的结果。通过订阅组合后的信号,就能拿到reduce处理后的解果,来判断登录按钮是否显示。
- 过滤
例1:过滤掉一些内容
[[_textField.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
//value源信号内容
//返回值为过滤的条件
return [value length] > 5 ; //只有当文本框内容大于5才能获取文本框内容
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"大于5时打印内容:%@",x);
}];
第一个block中直接返回想要过滤的条件,只有条件达成时,订阅信号subscribeNext的block内的方法才执行。
例2:忽略某个值
RACSubject *igSub = [RACSubject subject];
RACSignal *igSignal = [igSub ignore:@"1"];
[igSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[igSub sendNext:@"1"];
利用ignore方法设置要忽略的内容:“1”,当发送的消息(sendNext)为“1”时,订阅消息时就接收不到了。
例3:当前值和上一个值相同就不会被订阅
使用场景:当模型里一个属性没有变就不会更新UI界面,只有不同时才会刷新UI
RACSubject *disSub = [RACSubject subject];
[[disSub distinctUntilChanged] subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[disSub sendNext:@"1"];
[disSub sendNext:@"1"];
两次发送的都是“1”,但是block中只执行了一次。
- 标记
takeUntil需要一个信号作为标记,当标记的信号发送数据,就停止。
RACSubject * subject = [RACSubject subject];
RACSubject * subject1 = [RACSubject subject];
[[subject takeUntil:subject1] subscribeNext:^(id _Nullable x) {
NSLog(@"---%@---",x);
}];
[subject1 subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];
[subject1 sendNext:@"Stop"];
[subject sendNext:@"4"];
[subject sendNext:@"5"];
上面例子中subject1信号发送数据之后,subject就接收不到4和5了。
- rac_willDeallocSignal
对象销毁时发动的信号
// 监听文本框的改变,直到当前对象被销毁
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
以上例子中,当前对象被销毁时,会发送rac_willDeallocSignal信号,rac_textSignal就停止监听了。