1、ReactiveCocoa简介
ReactiveCocoa(简称为RAC),是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程(FP)和响应式编程(RP)的特性。所以,RAC也被描述为函数响应式编程(FRP)框架。 RAC框架的核心类是RACSignal, 所有的消息都是通过信号的方式进行传递,可以简单的理解这些信号就是一连串的状态,在状态发生改变的时候,对应的订阅者就会收到通知,然后执行相应的操作。
响应式编程思想:不需要考虑调用顺序,只需要知道考虑结果。类似于蝴蝶效应,产生一个事件,会影响很多东西。这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
比如在程序开发中 a = b + c ,赋值之后 b 或者 c 的值变化后,a 的值不会跟着变化。而采用响应式编程,则如果 b 或者 c 的数值发生变化,a 的数值会同时发生变化,即 a 的值与 b 和 c 绑定。
2、ReactiveCocoa主要应用场景
UI数据绑定
RAC可以方便的绑定任何数据流到UI控件上。
用户交互事件绑定
RAC可以为可交互的UI控件提供了一系列能发送Signal信号的方法。
统一的消息传递机制
RAC提供了统一的方法去处理异步的行为,包括delegate方法、blocks回调、target-action机制、notifications和KVO。
3、ReactiveCocoa常见类
思维导图
常见类
1、RACSiganl
信号类。只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。默认信号都是冷信号,也就是值改变了,也不会触发。只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。如何订阅信号:调用信号RACSignal的subscribeNext就能订阅
RACEmptySignal
:空信号,用来实现 RACSignal 的 +empty 方法;
RACDynamicSignal
:动态信号,使用一个 block 来实现订阅行为,我们在使用 RACSignal 的 +createSignal: 方法时创建的就是该类的实例;
RACErrorSignal
:错误信号,用来实现 RACSignal 的 +error: 方法;
2、RACSubscriber
表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过 createSignal 创建的信号,都有一个订阅者,帮助他发送数据。
3、RACDisposable
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它,也可以手动调用 [disposable dispose] 触发。
RAC使用流程
1、创建信号
2、订阅信号
3、发送信号
4、销毁信号 (需要的时候)
示例如下:
//1、创建信号(冷信号)
RACSignal * single = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
//block调用时刻:每当有订阅者订阅信号,就会调用block
//3、发送信号
[subscriber sendNext:@"发送信息"];
//如果不再发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
//block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅。执行完Block后,当前信号就不在被订阅了
NSLog(@"信号被销毁");
}];
}];
//2、订阅信号(该信号变成热信号)
RACDisposable *disposable = [single subscribeNext:^(id _Nullable x) {
//block调用时刻:每当有信号发送数据,就会调用该方法
NSLog(@"接收到数据:%@",x);
}];
//主动触发取消订阅
//[disposable dispose];
4、RACSubject
上面说到 RACSignal 是不具备发送信号的能力的,但是RACSubject
这个类就可以做到订阅/发送为一体。
使用场景:通常用来代替代理。
示例如下:
//1创建信号
RACSubject * subject = [RACSubject subject];
//2订阅信号
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
//3发送数据
[subject sendNext:@"发送数据"];
5、RACCommand
RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
使用场景:监听按钮点击、网络请求等。
示例如下:
//1、创建命令
RACCommand * loginBtnCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
NSLog(@"接收传过来的登录请求参数:%@",input);
//创建信号
return [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
NSLog(@"开始请求");
NSLog(@"请求中...");
NSLog(@"请求成功");
NSLog(@"处理数据");
//发送信号
[subscriber sendNext:@"我是请求到的数据"];
//请求完成
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
NSLog(@"结束了");
}];
}];
}];
/*
2、订阅loginBtnCommand命令事件中的信号发出的值
switchToLatest表示的是最新发送的信号
*/
[loginBtnCommand.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"接收到的请求数据:%@",x);
NSLog(@"登录成功,跳转页面");
}];
/*
3、监听loginBtnCommand监听命令是否执行完毕,默认会来一次,可以直接跳过
默认会来一次,可以直接跳过,skip表示跳过第一次命令)
*/
[[loginBtnCommand.executing skip:1] subscribeNext:^(NSNumber * _Nullable x) {
if ([x boolValue]) {
NSLog(@"正在执行中...");
} else {
NSLog(@"执行结束");
}
}];
//4、执行命令
[loginBtnCommand execute:@{@"account":@"1234567890",@"password":@"123456"}];
6、RACSequence
RAC中的集合类,用于代替NSArray、NSDictionary。
使用场景:可以使用它来快速遍历数组和字典。
示例如下:
//遍历数组
NSArray * arr = @[@"1",@"2",@"3",@"4",@"5"];
/*
第一步: 把数组转换成集合RACSequence numbers.rac_sequence
第二步: 把集合RACSequence转换RACSignal信号类 numbers.rac_sequence.signal
第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
*/
[arr.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"内容:%@",x);
}];
//遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"xiaoming",@"age":@18};
[dict.rac_sequence.signal subscribeNext:^(id x) {
//解包元组(x是元祖)
RACTupleUnpack(NSString *name,NSString *age) = x;
NSLog(@"%@ %@",name,age);
}];
7、RACScheduler
RAC中的队列,用GCD封装的。
示例如下:
//定时器
@weakify(self)
RACDisposable * disposable = [[RACSignal interval:2 onScheduler:[RACScheduler mainThreadScheduler]] subscribeNext:^(NSDate * _Nullable x) {
@strongify(self)
NSLog(@"当前时间:%@",x);
//关闭定时器
[disposable dispose];
}];
8、RACMulticastConnection
信号连接类。用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建。
示例如下:
/*
RACMulticastConnection:信号连接类
一般情况下,信号被订阅多少次,信号创建的block就被调用多少次。但是实际开发中,block只需调用一次就可以了。RACMulticastConnection可以实现不管订阅多少次信号,信号的block只调用一次。
如下代码:信号被订阅了3次,但是singal里面的block只被调用一次,即只输出一次“请求数据”
*/
//1、创建信号
RACSignal * singal = [RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
NSLog(@"请求数据");
[subscriber sendNext:@"请求数据"];
return nil;
}];
//2、把信号转成连接类
RACMulticastConnection * connection = [singal 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];
9、ReactiveCocoa常见宏
1、RAC(TARGET, [KEYPATH, [NIL_VALUE]])
:用于给某个对象的某个属性绑定。
//这里吧label的text属性绑定到textField改变信号中,textfield的内容发生改变的时候就会发出信号,label的text就会跟随着改变。
RAC(self.label,text) = _textfield.rac_textSignal;
2、RACObserve(self, name)
:监听某个对象的某个属性,返回的是信号。相当于kvo使用。
//监听self的age属性,x为属性的信息
[RACObserve(self, age) subscribeNext:^(id _Nullable x) {
NSLog(@"x:%@",x);
}];
3、RACTuplePack
:把数据包装成RACTuple(元组类)
4、RACTupleUnpack
:把RACTuple(元组类)解包成对应的数据
//元组
RACTuple * tuple = RACTuplePack(@1,@2,@3);//使用RACTuplePack宏来快速创建
NSLog(@"TUPLE:%@",tuple);
//使用RACTupleUnpack宏快速解包
RACTupleUnpack(NSNumber *num1,NSNumber *num2,NSNumber *num3) = tuple;
NSLog(@"num1:%@",num1);
NSLog(@"num2:%@",num2);
NSLog(@"num3:%@",num3);
//使用下标的方式来获取
NSLog(@"第0个:%@",tuple[0]);
10、 ReactiveCocoa常见用法
1、rac_signalForSelector
:用于替代代理。
/*
代替代理
rac_signalForSelector:把调用某个对象的方法的信息转换成信号,调用这个方法,就会发送信号。
当redView中的testButton按钮被点击了(即调用了testButtonClick),就会发出信号
*/
[[redView rac_signalForSelector:@selector(testButtonClick:)] subscribeNext:^(id x) {
NSLog(@"redView中的testButton按钮被点击了");
}];
2、rac_valuesAndChangesForKeyPath
:代替KVO,用于监听某个对象的属性改变。
/*
KVO
把监听redView的center属性改变转换成信号,只要值改变就会发送信号
x为属性的信息
*/
[[redView rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"center:%@",x);
}];
3、rac_signalForControlEvents
:用于监听某个事件。
/*
监听事件
把按钮点击事件转换为信号,点击按钮,就会发送信号
x为Button的信息
*/
[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"按钮被点击了");
}];
4、rac_addObserverForName
:用于监听某个通知。
/*
代替通知
把监听到的通知转换信号
*/
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
5、rac_textSignal
:监听文本框文字改变。
//监听文本框的文字改变
[self.passwordTextField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
NSLog(@"密码框输入的内容%@",x);
}];
11、ReactiveCocoa组合用法
1、concat
:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号
//当需要按顺序执行的时候: 先执行C, 在执行D
RACSubject *subC = [RACSubject subject];
RACSubject *subD = [RACReplaySubject subject];
NSMutableArray *array2 = [NSMutableArray array];
//订阅信号
[[subC concat:subD] subscribeNext:^(id _Nullable x) {
[array2 addObject:x];
}];
//发送信号
[subD sendNext:@"D"];
[subC sendNext:@"C"];
[subC sendCompleted];
//输出: [C, D]
NSLog(@"%@", array2);
2、then
:用于连接两个信号,当第一个信号完成,才会连接then返回的信号
RACSubject *subjectA = [RACReplaySubject subject];
RACSubject *subjectB = [RACReplaySubject subject];
//发送信号
[subjectA sendNext:@"A"];
[subjectA sendCompleted];
[subjectB sendNext:@"B"];
//订阅信号
[[subjectA then:^RACSignal * _Nonnull{
return subjectB;
}] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//这里只会输出: B 不会输出: A
3、merge
:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用
// 只要想无序的整合信号数据
RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
RACSubject *subjectC = [RACSubject subject];
//合并信号
RACSignal *single = [[subjectA merge:subjectB] merge:subjectC];
//订阅信号
[single subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//发出消息
[subjectA sendNext:@"A"];
[subjectC sendNext:@"C"];
[subjectB sendNext:@"B"];
//输出结果(分别输出): A, C, B
4、zipWith
:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件
// 只要想无序的整合信号数据
RACSubject *subjectA = [RACSubject subject];
RACSubject *subjectB = [RACSubject subject];
//合并信号
RACSignal *single = [subjectA zipWith:subjectB];
//订阅信号
[single subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//发出消息
[subjectA sendNext:@"A"];
[subjectB sendNext:@"B"];
//输出: 元祖(A, B)
5、combineLatest
:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号
6、reduce(聚合
):用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
@weakify(self)
RAC(self.loginButton, enabled) = [RACSignal combineLatest:@[self.phoneTextField.rac_textSignal,self.passwordTextField.rac_textSignal] reduce:^id _Nonnull(NSString * account, NSString * password){
@strongify(self)
if (account.length && (password.length > 5)) {
self.loginButton.backgroundColor = [UIColor blueColor];
return @(YES);
}else{
self.loginButton.backgroundColor = [UIColor lightGrayColor];
return @(NO);
}
}];
7、filter
:过滤,每次信号发出,会先执行过滤条件判断
[[_accountText.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
//类似手机号的输入, 只有等于11位的时候才返回true
return value.length == 11;
}] subscribeNext:^(NSString * _Nullable x) {
//这里只会返回等于11位的字符
NSLog(@"filter = %@", x);
}];
8、ignore
:内部调用filter过滤,忽略掉ignore的值
//当88时,下面的log不输出
[[self.phoneTextField.rac_textSignal ignore:@"88"] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"ignore = %@", x);
}];
9、distinctUntilChanged
:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。常用于UI刷新,即只有两次数据不一样才需要刷新UI。
//创建信号
RACSubject *subject = [RACSubject subject];
//订阅
[[subject distinctUntilChanged] subscribeNext:^(id _Nullable x) {
NSLog(@"distinctUntilChanged = %@", x);
}];
[subject sendNext:@12];
[subject sendNext:@12];
[subject sendNext:@23];
/*
输出结果:只会输出两次
distinctUntilChanged = 12
distinctUntilChanged = 23
*/
10、take
:从开始一共取N次的信号, 当遇到sendCompleted
语句执行时, 会提前停止发送信号
RACSubject *subject1 = [RACSubject subject];
[[subject1 take:2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[subject1 sendNext:@1];
[subject1 sendNext:@2];
[subject1 sendNext:@3];
//输出: 1, 2
//如果上面发送信号的代码调整为
[subject1 sendNext:@1];
[subject1 sendCompleted];
[subject1 sendNext:@2];
[subject1 sendNext:@3];
//那么输出结果将会,只输出: 1
11、takeLast
:取调用sendCompleted
之前的N次信号,前提条件,订阅者必须调用sendCompleted
,否则不会执行任何操作
RACSubject *subject1 = [RACSubject subject];
[[subject1 takeLast:2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[subject1 sendNext:@1];
[subject1 sendNext:@2];
[subject1 sendNext:@3];
[subject1 sendCompleted];
/*
输出:
2
3
*/
12、takeUntil
:只要传入的信号发送完成或者subject2
开始发送信号的时候,就不会再接收信号的内容
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
[[subject1 takeUntil:subject2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
[subject1 sendNext:@11];
[subject1 sendNext:@12];
// [subject1 sendCompleted];
[subject1 sendNext:@13];
[subject2 sendNext:@"21"];
[subject2 sendNext:@"22"];
//输出: 11, 12, 13
//当sendCompleted取消注释的时候, 只会输出: 11, 12
13、skip
:跳过N个信号后, 再开始订阅信号
//创建信号
RACSubject *subject = [RACSubject subject];
//订阅信号(要求跳过2个信号)
[[subject skip:2] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//发送信号
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
[subject sendNext:@4];
//因为上面跳过了两个信号, 所以这里只会输出: 3, 4
14、switchToLatest
:用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号
//信号的信号
RACSubject *subject1 = [RACSubject subject];
RACSubject *subject2 = [RACSubject subject];
//获取信号中信号最近发出信号,订阅最近发出的信号
[[subject1 switchToLatest] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
//subject1发送subject2信号
[subject1 sendNext:subject2];
[subject2 sendNext:@"信号中信号"];
//最终结果输出: "信号中信号"
15、timeout
:超时, 可以让一个信号在一定时间后自动报错
//timeout: 超时, 可以让一个信号在一定时间后自动报错
RACSignal *single = [[RACSignal createSignal:^RACDisposable * _Nullable(id _Nonnull subscriber) {
return nil;
}] timeout:2 onScheduler:[RACScheduler currentScheduler]];
[single subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
//2秒后自动调用
NSLog(@"%@", error);
}];
12、注意
1、系统要求:ReactiveCocoa 要求 OS X 10.8+ 以及 iOS 8.0+。
2、强引用问题
@weakify 和@strongify 这个两个宏是为了解决在使用block的时候强引用问题,注意这两个宏成对使用才有效。
@weakify(self)
[[self.forgotPasswordButton rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
@strongify(self)
//发送数据
[self.forgotPasswordButtonRACSubject sendNext:nil];
}];