一、RAC介绍
RAC 是一个 iOS 中的函数式响应式编程框架,一般与MVVM配套使用。
在非RAC开发中,都是习惯赋值。在RAC开发中,需要改变开发思维,由赋值转变为绑定,并不需要重写set方法。
RAC项目的Podfile如下:
use_frameworks!
target '工程名称' do
pod 'ReactiveObjC', '~> 3.0.0'
end
如果使用Swift作为开发语言,建议使用RXSwift。关于RXSwift,以后会单独写一篇文章介绍。
RAC使用注意事项:
1.RAC学习曲线陡峭,团队开发时要谨慎使用,确保项目组所有成员都会RAC,再使用RAC构建项目。如果一个人开发一个项目,要使用RAC,先跟项目经理商量一下,不要自己想当然的想用就用,这样如果公司要加人,会增加招人成本,公司不一定乐意。
2.在使用过程中,尽量保持同一个项目的成员,代码风格一致,这样方便管理维护。
3.关于学习曲线陡峭,有的同学可能不以为然,随便写个demo,体验了几个RAC基本操作方法,就觉得自己已经会用RAC了。其实不然,要真正的用RAC构造整个项目,再配上MVVM,是需要一个较长学习周期的。
二、RAC常见用法
- 代替代理
rac_signalForSelector:用于替代代理
// 需求:自定义redView,监听红色view中按钮点击
// 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
// rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
// 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"点击红色按钮");
}];
- 代替KVO
rac_valuesAndChangesForKeyPath:用于监听某个对象的属性改变。
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- 监听事件 (代替addTarget)
rac_signalForControlEvents:用于监听某个事件。
[[self.btn rac_signalForControlEvents:(UIControlEventTouchUpInside)]subscribeNext:^(__kindof UIControl * _Nullable x) {
NSLog(@"%@",x);
}];
- 代替通知
rac_addObserverForName:用于监听某个通知
// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
- 监听文本框文字改变
rac_textSignal:只要文本框发出改变就会发出这个信号
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改变了%@",x);
}];
三、常见类解释
RactiveCocoa中很重要的两个class,一个是RACSignal,一个是RACSequence,而这两个class的super class就是RACStream。
RACStream
: 表示一个基本单元可以为任意值,其值会随着事件的变化而变化。可以在其上进行一些复杂的操作运算(map,filter,skip,take等.)此类不会被经常使用, 多情况下表现为signal和sequences(RACSignal 和RACSequence继承于RACStream类)。
RACSiganl
:只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发。signal能发送3种不同类型的事件:Next,Completed,Error。
信号三部曲:创建信号、订阅信号、发送信号。
// 1.创建信号
RACSignal *siganl = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 2.发送信号,必须是一个对象类型
[subscriber sendNext:@1];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
//return nil;
return [RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];
}];
// 3.订阅信号,才会激活信号.
[siganl subscribeNext:^(id x) {
// block调用时刻:每当有信号发出数据,就会调用block.
NSLog(@"接收到数据:%@",x);
}];
RACSubject
: 信号提供者,自己可以充当信号,又能发送信号。
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第一个订阅者%@",x);
}];
[subject subscribeNext:^(id x) {
// block调用时刻:当信号发出新值,就会调用.
NSLog(@"第二个订阅者%@",x);
}];
// 3.发送信号
[subject sendNext:@"1"];
RACReplaySubject
:重复提供信号类,RACSubject的子类。
RACDisposable
:用于取消订阅或者清理资源,在一个completed或者error事件之后,就会自动触发它,订阅会自动移除。也可以通过RACDisposable 手动移除订阅。
RACChannelTerminal :通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
- (RACSignal *)bindTextField:(UITextField *)textField slider:(UISlider *)slider
{
//通道终端,代表 RACChannel 的一个终端,用来实现双向绑定。
RACChannelTerminal *sliderChanel = [slider rac_newValueChannelWithNilValue:nil];
RACChannelTerminal *textFieldChanel = [textField rac_newTextChannel];
//双向绑定
[[textFieldChanel map:^id _Nullable(id _Nullable value) {
return @([value floatValue]);
}] subscribe:sliderChanel]; //输入框的值流向滑杆
[[sliderChanel map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"%.2f", [value floatValue]];
}] subscribe:textFieldChanel]; //滑杆的值流向输入框
//merge合并信号,任何一个信号发送数据,都能监听到。
return [textFieldChanel merge:sliderChanel];
}
//-----------------------------
//功能实现:绑定滑杆和输入框,滑杆滑动输入框的内容跟随变化,输入框值改变,滑杆自动滑动到对应值位置。当三个滑杆或者输入框的值均改变过一次时,colorView的颜色作出改变。
- (void)demo1
{
_redInput.text = _greenInput.text = _blueInput.text = @"0.5";
RACSignal *redSignal = [self bindTextField:_redInput slider:_redSlider];
RACSignal *greenSignal = [self bindTextField:_greenInput slider:_greeSlider];
RACSignal *blueSignal = [self bindTextField:_blueInput slider:_blueSlider];
//将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
RAC(_colorView, backgroundColor) = [[RACSignal combineLatest:@[redSignal, greenSignal, blueSignal]] map:^id _Nullable(RACTuple * _Nullable value) {
return [UIColor colorWithRed:[value[0] floatValue]
green:[value[1] floatValue]
blue:[value[2] floatValue]
alpha:1];
}];
}
RACTuple
:元组类,类似NSArray,用来包装值.
RACSequence
:RAC中的集合类,用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典,达到对值的一些过滤和转换。
// 遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
// 相当于以下写法
// NSString *key = x[0];
// NSString *value = x[1];
NSLog(@"%@ %@",key,value);
}];
RACCommand
:RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
// 一、RACCommand使用步骤:
// 1.创建命令 initWithSignalBlock:(RACSignal * (^)(id input))signalBlock
// 2.在signalBlock中,创建RACSignal,并且作为signalBlock的返回值
// 3.执行命令 - (RACSignal *)execute:(id)input
// 二、RACCommand使用注意:
// 1.signalBlock必须要返回一个信号,不能传nil.
// 2.如果不想要传递信号,直接创建空的信号[RACSignal empty];
// 3.RACCommand中信号如果数据传递完,必须调用[subscriber sendCompleted],这时命令才会执行完毕,否则永远处于执行中。
// 4.RACCommand需要被强引用,否则接收不到RACCommand中的信号,因此RACCommand中的信号是延迟发送的。
// 三、RACCommand设计思想:内部signalBlock为什么要返回一个信号,这个信号有什么用。
// 1.在RAC开发中,通常会把网络请求封装到RACCommand,直接执行某个RACCommand就能发送请求。
// 2.当RACCommand内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock返回的信号传递了。
// 四、如何拿到RACCommand中返回信号发出的数据。
// 1.RACCommand有个执行信号源executionSignals,这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
// 2.订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
// 五、监听当前命令是否正在执行executing
// 六、使用场景,监听按钮点击,网络请求
// 1.创建命令
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令");
// 创建空信号,必须返回信号
// return [RACSignal empty];
// 2.创建信号,用来传递数据
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"请求数据"];
// 注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
return nil;
}];
}];
// 强引用命令,不要被销毁,否则接收不到数据
_loginCommand = command;
// 3.订阅RACCommand中的信号
[command.executionSignals subscribeNext:^(id x) {
//订阅executionSignals就能拿到RACCommand中返回的信号,然后订阅signalBlock返回的信号,就能获取发出的值。
[x subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}];
// RAC高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
/* [command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];*/
// 4.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
// executing:判断当前的block是否在执行,执行完之后会返回NO
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
// 执行完成
NSLog(@"执行完成");
}
}];
// 5.执行命令(控制器里执行此句代码)
[self.login_vm.loginCommand execute:nil];
RACMulticastConnection
:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。
使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建。
RACScheduler
:RAC中的队列,用GCD封装的。
RACUnit
:表⽰stream不包含有意义的值,也就是看到这个,可以直接理解为nil.
四、RAC常用宏
1、RAC 绑定一个信号
RAC宏允许直接把信号的输出应用到对象的属性上 每次信号产生一个next事件,传递过来的值都会应用到该属性上
//1、RAC 把一个对象的摸个属性绑定一个信号,只有发出信号,就会吧信号的内容给对象的属性赋值。
//这里吧label的text属性绑定到textField改变信号中,textfield的内容发生改变的时候就会发出信号,只要文本框文字改变,就会修改label的文字。
RAC(self.label,text) = _textfield.rac_textSignal;
2、RACObserve 相当于kvo使用
//person为一个模型,只要模型里面的name字段改变,就会把最新的name值显示在label上
[RACObserve(self.person, name) subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
self.nameLabel.text = x;
}];
3、@weakify 和@strongify 解决循环引用,注意这两个宏是配套使用才有效。
@weakify(self);
[[self.testTextFileld rac_textSignal] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"%@",x);
@strongify(self);
self.label.text = x;
}];
4、 RACChannelTo 用于双向绑定
RACChannelTerminal也可以实现双向绑定,例子上面已写
RACChannelTo(view, property) = RACChannelTo(model, property);
5、RACTuplePack:把数据包装成RACTuple(元组类)
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@(1),@(30));
6、RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"xmg",@20);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
// name = @"xmg" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
五、常见操作方法
所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,因此只要继承RACStream就有了操作处理方法。
1. bind(绑定) 核心方法
ReactiveCocoa 操作的核心方法是 bind(绑定),而且也是RAC中核心开发方式。之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
RAC底层都是调用bind, 在开发中很少直接使用 bind 方法,bind属于RAC中的底层方法,我们只需要调用封装好的方法,bind用作了解即可.
2. flattenMap
把源信号的内容映射成一个新的信号,信号可以是任意类型
使用步骤:
1.传入一个block,block类型是返回值RACStream,参数value
2.参数value就是源信号的内容,拿到源信号的内容做处理
3.包装成RACReturnSignal信号,返回出去
//监听文本框的内容改变,重新映射成一个新的信号
[[_textField.rac_textSignal flattenMap:^RACStream *(id value) {
// block调用时机:信号源发出的时候
// block作用:改变信号的内容
// 返回RACReturnSignal (需要导入RACReturnSignal.h)
return [RACReturnSignal return:[NSString stringWithFormat:@"信号内容:%@", value]];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
3. Map
把源信号的值映射成一个新的值
如果map的是一个sequence,那么会遍历里面的么一个元素,遍历的每一个值,都会做相同的处理
//监听文本框的内容改变,映射成一个新值.
[[_textField.rac_textSignal map:^id(id value) {
//把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
return [NSString stringWithFormat:@"信号内容: %@", value];
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
FlatternMap 和 Map 的区别
FlatternMap 中的Block 返回信号。
Map 中的Block 返回对象。
开发中,如果信号发出的值 不是信号 ,映射一般使用 Map
如果信号发出的值 是信号,映射一般使用 FlatternMap。
信号中的信号(signalOfsignals)用flatternMap:
// 创建信号中的信号
RACSubject *signalOfsignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
[[signalOfsignals flattenMap:^RACStream *(id value) {
// 当signalOfsignals的signals发出信号才会调用
return value;
}] subscribeNext:^(id x) {
// 只有signalOfsignals的signal发出信号才会调用,因为内部订阅了bindBlock中返回的信号,也就是flattenMap返回的信号。
// 也就是flattenMap返回的信号发出内容,才会调用。
NSLog(@"signalOfsignals:%@",x);
}];
// 信号的信号发送信号
[signalOfsignals sendNext:signal];
// 信号发送内容
[signal sendNext:@"hi"];
------- 组合 -------
组合就是将多个信号按照某种规则进行拼接,合成新的信号。
4. concat
按顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
//拼接信号 signalA、 signalB、 signalC
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"Hello"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"World"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalC = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"!"];
[subscriber sendCompleted];
return nil;
}];
// 拼接 A B, 把signalA拼接到signalB后,signalA发送完成,signalB才会被激活。
RACSignal *concatSignalAB = [signalA concat:signalB];
// A B + C
RACSignal *concatSignalABC = [concatSignalAB concat:signalC];
// 订阅拼接的信号, 内部会按顺序订阅 A->B->C
// 注意:第一个信号必须发送完成,第二个信号才会被激活...
[concatSignalABC subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
5. then
用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
then方法会等待completed事件的发送,然后再订阅由then block返回的signal。这样就高效地把控制权从一个signal传递给下一个。
注意使用then,之前信号的值会被忽略掉
6. merge
合并信号,任何一个信号发送数据,都能监听到。
合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
合并信号一被订阅,就会订阅里面所有的信号。
只要有一个信号被发出就会被监听。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B"];
return nil;
}];
// 合并信号, 任何一个信号发送数据,都能监听到
RACSignal *mergeSianl = [signalA merge:signalB];
[mergeSianl subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
//输出
//A
//B
7. combineLatest
将多个信号合并起来,并且拿到各个信号最后一个值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
必须两个信号都发出内容,才会被触发, 并且把两个信号的 最后一次 发送的值组合成元组发出。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A1"];
[subscriber sendNext:@"A2"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B1"];
[subscriber sendNext:@"B2"];
[subscriber sendNext:@"B3"];
return nil;
}];
RACSignal *combineSianal = [signalA combineLatestWith:signalB];
[combineSianal subscribeNext:^(id x) {
NSLog(@"combineLatest:%@", x);
}];
/*
输出
2018-02-01 17:39:04.914804+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60400000fc70> (
A2,
B1
)
2018-02-01 17:39:04.915092+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000faa0> (
A2,
B2
)
2018-02-01 17:39:04.915270+0800 RAC 101[21958:29964626] combineLatest:<RACTwoTuple: 0x60000000fff0> (
A2,
B3
)
*/
//如果用[signalB combineLatestWith:signalA],那么结果就是(B3, A1) (B3, A2)
8. reduce
把信号发出元组的值聚合成一个值
reduce 合并信号的数据,进行汇总计算使用
订阅聚合信号,每次有内容发出,就会执行reduceblcok,把信号内容转换成reduceblcok返回的值。
常见的用法,(先组合在聚合)一般跟combineLatest 一起使用
//判断用户名和密码同时存在的时候,按钮才能点击
[[RACSignal combineLatest:@[self.nameField.rac_textSignal, self.passwordField.rac_textSignal] reduce:^id _Nullable(NSString *name, NSString *pwd){
return @(name.length > 0 && pwd.length > 0);
}] subscribeNext:^(id _Nullable x) {
_btn.enabled = [x boolValue];
}];
9. zip
把两个信号压缩成一个信号,只有当两个信号 同时 发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
1. 定义压缩信号,内部就会自动订阅signalA,signalB
2. 每当signalA或者signalB发出信号,就会判断signalA,signalB有没有发出个信号,有就会把每个信号 第一次 发出的值包装成元组发出
注意:combineLatest与zip用法相似,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
RACSignal *signalA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"A1"];
[subscriber sendNext:@"A2"];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"B1"];
[subscriber sendNext:@"B2"];
[subscriber sendNext:@"B3"];
return nil;
}];
RACSignal *zipSignal = [signalA zipWith:signalB];
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
结果:
2018-02-01 18:00:04.585460+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x604000005840> (
A1,
B1
)
2018-02-01 18:00:04.585790+0800 RAC 101[22558:30088028] <RACTwoTuple: 0x6040000058d0> (
A2,
B2
)
*/
10. filter
过滤信号,使用它可以获取满足条件的信号
// 每次信号发出,会先执行过滤条件判断.
[[_textField.rac_textSignal filter:^BOOL(NSString *value) {
NSLog(@"原信号: %@", value);
// 过滤 长度 <= 3 的信号
return value.length > 3;
}] subscribeNext:^(id x) {
NSLog(@"长度大于3的信号:%@", x);
}];
11. ignore
忽略某些信号
底层调用了 filter 与 过滤值进行比较,若相等返回则 NO
//过滤掉值为111的信号
[[_textField.rac_textSignal ignore:@"111"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
12. distinctUntilChanged
当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
[[_textField.rac_textSignal distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
13. skip
跳过第N次的发送的信号。
// 表示输入第一次,不会被监听到,跳过第一次发出的信号
[[_textField.rac_textSignal skip:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
14. take
取前N次的发送的信号。
RACSubject *subject = [RACSubject subject] ;
// 取 前两次 发送的信号
[[subject take:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 输出
2017-01-03 17:35:54.566 ReactiveCocoa进阶[4969:1677908] 1
2017-01-03 17:35:54.567 ReactiveCocoa进阶[4969:1677908] 2
15. takeLast
取最后N次的发送的信号
前提条件,订阅者必须调用完成 sendCompleted,因为只有完成,就知道总共有多少信号.
RACSubject *subject = [RACSubject subject] ;
// 取 后两次 发送的信号
[[subject takeLast:2] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 必须 跳用完成
[subject sendCompleted];
16. takeUntil
获取信号直到某个信号执行完成
// 监听文本框的改变直到当前对象被销毁
[_textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
17. switchToLatest
用于signalOfSignals(信号的信号),有时候信号也会发出信号,会在signalOfSignals中,获取signalOfSignals发送的最新信号。
switchToLatest:只能用于信号中的信号
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
// 获取信号中信号最近发出信号,订阅最近发出的信号。
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signalOfSignals sendNext:signal];
[signal sendNext:@1];
---- 秩序 ----
秩序包括 doNext 和 doCompleted 这两个方法,主要是在 执行sendNext 或者 sendCompleted 之前,先执行这些方法中Block。
18. doNext
执行sendNext
之前,会先执行这个doNext
的 Block
19. doCompleted
执行sendCompleted
之前,会先执行这doCompleted
的Block
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"hi"];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 执行 [subscriber sendNext:@"hi"] 之前会调用这个 Block
NSLog(@"doNext");
}] doCompleted:^{
// 执行 [subscriber sendCompleted] 之前会调用这 Block
NSLog(@"doCompleted");
}] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
运行结果:
2018-02-02 09:56:39.206710+0800 RAC 101[40487:30848792] doNext
2018-02-02 09:56:39.206954+0800 RAC 101[40487:30848792] hi
2018-02-02 09:56:39.207112+0800 RAC 101[40487:30848792] doCompleted
*/
---- 线程 ----
ReactiveCocoa 中的线程操作 包括 deliverOn
和 subscribeOn
这两种,将 传递的内容 或 创建信号时 block中的代码 切换到指定的线程中执行。
20. deliverOn
内容传递切换到指定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。
// 在子线程中执行
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"%@", [NSThread currentThread]);
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}]
deliverOn:[RACScheduler mainThreadScheduler]]
subscribeNext:^(id x) {
NSLog(@"%@", x);
NSLog(@"%@", [NSThread currentThread]);
}];
});
/*
运行结果:
2018-02-02 14:10:28.938587+0800 RAC 101[45182:31032478] <NSThread: 0x604000279c00>{number = 3, name = (null)}
2018-02-02 14:10:29.076247+0800 RAC 101[45182:31031131] 123
2018-02-02 14:10:29.076494+0800 RAC 101[45182:31031131] <NSThread: 0x60400006f540>{number = 1, name = main}
*/
可以看到 副作用 在 子线程 中执行,而 传递的内容 在 主线程 中接收
21. subscribeOn
subscribeOn则是将 内容传递 和 副作用 都会切换到指定线程中。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"%@", [NSThread currentThread]);
[subscriber sendNext:@123];
[subscriber sendCompleted];
return nil;
}]
subscribeOn:[RACScheduler mainThreadScheduler]] //传递的内容到主线程中
subscribeNext:^(id x) {
NSLog(@"%@", x);
NSLog(@"%@", [NSThread currentThread]);
}];
});
/*
运行结果:
2018-02-02 14:15:23.956524+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
2018-02-02 14:15:23.956737+0800 RAC 101[45319:31066198] 123
2018-02-02 14:15:23.956961+0800 RAC 101[45319:31066198] <NSThread: 0x600000079940>{number = 1, name = main}
*/
可以看到,内容传递 和 副作用 都切换到了 主线程 执行
----时间----
时间操作就会设置信号超时,定时和延时。
22. interval
定时:每隔一段时间发出信号
// 每隔1秒发送信号,指定当前线程执行
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"定时:%@", x);
}];
23. timeout
超时,可以让一个信号在一定的时间后,自动报错。
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
// 不发送信号,模拟超时状态
// [subscriber sendNext:@"hello"];
//[subscriber sendCompleted];
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];// 设置1秒超时
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"%@", error);
}];
// 执行代码 1秒后 输出:
2017-01-04 13:48:55.195 ReactiveCocoa进阶[1980:492724] Error Domain=RACSignalErrorDomain Code=1 "(null)"
24. retry
重试:只要 发送错误 sendError: 就会重新执行创建信号的Block直到成功
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 3) {
[subscriber sendNext:@"Hello"];
} else {
// 发送错误
NSLog(@"收到错误:%d", i);
[subscriber sendError:nil];
}
i++;
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"%@", x);
} error:^(NSError *error) {
NSLog(@"%@", error);
}];
// 输出
2017-01-04 14:36:51.594 ReactiveCocoa进阶[2443:667226] 收到错误信息:0
2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:1
2017-01-04 14:36:51.595 ReactiveCocoa进阶[2443:667226] 收到错误信息:2
2017-01-04 14:36:51.596 ReactiveCocoa进阶[2443:667226] Hello
25. replay
重放:当一个信号被多次订阅,反复播放内容
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendNext:@2];
return nil;
}] replay];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[signal subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
/*
运行结果:
2018-02-02 14:37:45.269758+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270018+0800 RAC 101[45938:31201667] 2
2018-02-02 14:37:45.270175+0800 RAC 101[45938:31201667] 1
2018-02-02 14:37:45.270300+0800 RAC 101[45938:31201667] 2
/*
26. throttle
节流:当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
RACSubject *subject = [RACSubject subject];
// 节流1秒,1秒后接收最后一个发送的信号
[[subject throttle:1] subscribeNext:^(id x) {
NSLog(@"%@", x);
}];
[subject sendNext:@1];
[subject sendNext:@2];
[subject sendNext:@3];
// 输出
2018-02-02 14:53:00.234975+0800 RAC 101[46278:31282576] 3
扩展:利用RAC封装网络请求
通过RAC,我们可以奖网络请求封装为信号的形式,数据的获取可以通过订阅信号的方式来得到。与传统的网络请求相比,是不是感觉耳目一新?
#import <AFNetworking/AFNetworking.h>
#import "AFNetworking.h"
#import <ReactiveObjC.h>
@interface NetworkingManager : AFHTTPSessionManager
+ (instancetype)shareManager;
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters;
@end
--------------------------------------------------------------------
@implementation NetworkingManager
+ (instancetype)shareManager
{
static NetworkingManager *manager;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [[self alloc]init];
manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/json", @"text/javascript",@"text/html" ,nil];
});
return manager;
}
//GET请求
- (RACSignal *)GET:(NSString *)url parameters:(id)parameters
{
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[self GET:url parameters:parameters progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
[subscriber sendNext:responseObject];
[subscriber sendCompleted];
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
[subscriber sendError:error];
}];
return nil;
}];
}
@end
--------------------------------------------------------------------
//请求网络数据
- (void)fetchWeatherData
{
NetworkingManager *manager = [NetworkingManager shareManager];
[[manager GET:@"http://www.weather.com.cn/data/sk/101010100.html" parameters:nil] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
} error:^(NSError * _Nullable error) {
NSLog(@"%@", error.localizedDescription);
}];
}
//如上:调用请求,订阅信号后得到的x就是需要的json数据。
网友评论