iOS - RAC使用(一)
前言
由于公司需使用RAC+MVVM模式要写业务逻辑,对于rac菜鸡的我,不得不补习下功课,在RAC中,万物皆信号。
感悟:
没使用之前,为嘛要用这个,不用这个也能实现啊
使用中,这玩意怎么写,妈呀,还能这样操作啊...
使用之后,这玩意不得不说真牛逼
大纲
RAC大纲目录
一、RAC简介
二、编程思想
三、RAC知识集结站
3.0 RAC之RACSignal
、RACSubject
、RACReplaySubject
的简单使用
3.1 RAC之集合(遍历数组/字典、字典转模型、映射)
3.2 常见使用场景(代替代理/kvo/通知/监听事件/监听文本框文字改变/rac_liftSelector<处理当界面多个请求时,需要都获取数据时,才能展示界面>)
3.3 常见宏
3.4 RACMulticastConnection
3.5 RACCommand
3.6 RAC核心 (绑定信号) 开发中很少用bind
3.7 RAC 操作方法之映射
3.8 RAC 组合(concat
、then
、merge
)
3.9 RAC 核心操作方法过滤
一、RAC简介
ReactiveCocoa(简称为RAC
),是由github
开源的一个应用于于iOS和OS开发的框架。Swift版本
Swift版本的RAC——(ReactiveCocoa),目前星星指数19.8k,最新版本为10.2。OC版本的RAC——ReactiveObjC,目前星星指数2.2k,最新版本为10.2
ReactiveCocoa
为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合
的思想。
二、编程思想
编程最主要的是TA的思想是怎样,那么RAC又是属于哪种编程思想呢?
面向过程:处理事情以过程为核心,一步一步的实现
面向对象:万物皆对象
链式编程思想: 是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。(eg: masonry中的 make.width.height.equalTo(@(16));
)
链式编程思想特点
:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
函数式编程思想:是把操作尽量写成一系列嵌套的函数或者方法调用。(eg:ReactiveCocoa)
函数式编程本质:
就是往方法中传入Block,方法中嵌套Block调用,把代码聚合起来管理
函数式编程特点:
每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
*综上
RAC操作思想:运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术.
Hook用处:截获API调用的技术。
Hook原理:在每次调用一个API返回结果之前,先执行你自己的方法,改变结果的输出。
RAC开发方式:RAC中核心开发方式,也是绑定,之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
eg:把数据展示到控件上,之前都是重写控件的setModel方法,用RAC就可以在一开始创建控件的时候,就绑定好数据。
RAC
结合了几种编程风格:
- 函数式编程(
Functional Programming
) - 响应式编程(
Reactive Programming
)
故RAC
这个框架也可以说是函数响应式编程(FRP
)框架
以后使用RAC
解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
三、RAC知识集结站
学习框架的重点在于:先要搞清楚框架中常用的类,而在
RAC
中最核心的类RACSiganl
3.0 RAC之RACSignal
、RACSubject
、RACReplaySubject
的简单使用
RACSignal的简单使用
RACSignal:
信号类,一般表示将来有数据传递,只要有数据改变,信号内部接收到数据,就会马上发送数据。
- 在
RAC
中最核心的类是RACSignal
-
信号类(RACSiganl)
:只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出 - 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发
- 如何订阅信号:调用信号RACSignal的subscribeNext就能订阅信号
RACSignal使用步骤:
- 1、创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
- 2、订阅信号,才会激活信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
- 3、发送信号
- (void)sendNext:(id)value
eg:
//1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//didSubscribe调用:只要一个信号被订阅了就会调用
//didSubscribe作用:发送数据
//2.发送信号
[subscriber sendNext:@"111"];
// 如果不在发送数据,最好发送信号完成,内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
// block调用:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
// }];
}];
// 3.订阅信号
[signal subscribeNext:^(id _Nullable x) {
//nextBlock调用:每当有信号发出数据,就会调用block
//nextBlock作用:处理数据,展示到UI上面
//x:信号发送的内容
NSLog(@"x接收数据的值为: %@",x); //x接收数据的值为: 111
}];
//RACSubscriber:
//表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。
//通过create创建的信号,都有一个订阅者,帮助他发送数据。
//RACDisposable:用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
//使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。
RACSignal底层实现:
-
1、创建信号,内部会创建
一个RACDynamicSignal
,首先把didSubscribe
保存到信号中,还不会触发。 -
2、当信号被订阅,也就是调用
signal
的subscribeNext:nextBlock
-
2.1、
subscribeNext
内部会创建订阅者subscriber
,并且把nextBlock
保存到subscriber
中。 -
2.2、
subscribeNext
内部会调用siganl
的didSubscribe
-
-
3、
siganl
的didSubscribe
中调用[subscriber sendNext:@"111"];
- 3.1、
sendNext
底层其实就是执行subscriber
的nextBlock
- 3.1、
RACSubject、RACReplaySubject的简单使用
RACSubject:
信号提供者,自己可以充当信号,又能发送信号。
使用场景:
用来代替代理
RACReplaySubject:
重复提供信号类,RACSubject
的子类。
RACReplaySubject与RACSubject区别:
RACReplaySubject
可以先发送信号,在订阅信号,RACSubject
就不可以。
RACSubject使用步骤:
- 1、创建信号
[RACSubject subject]
,跟RACSiganl
不一样,创建信号时没有block
。 - 2、订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
- 3、发送信号
sendNext:(id)value
eg:
//RACSubject底层实现:遍历所有的订阅者,调用nextblock 能被多次订阅
//RACSubject被订阅,仅仅是保存订阅者
//RACSubject发送数据,遍历所有的订阅,调用他们的nextblock
//1.创建信号
RACSubject *subject = [RACSubject subject];
//2.订阅信号
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"x接收数据的值为:%@",x);//x接收数据的值为:111
}];
//3.发送数据
[subject sendNext:@"111"];
RACSubject底层实现:
- 1.调用
subscribeNext
订阅信号,只是把订阅者保存起来,并且订阅者的nextBlock
已经赋值了。 - 2.调用
sendNext
发送信号,遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock
。
RACReplaySubject使用步骤:
- 1.创建信号
[RACReplaySubject subject]
,跟RACSiganl
不一样,创建信号时没有block
。 - 2.可以先订阅信号,也可以先发送信号。
- 2.1 订阅信号
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
- 2.2 发送信号
sendNext:(id)value
- 2.1 订阅信号
eg:
// 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。
// 也就是先保存值,在订阅值。
// 1.创建信号
RACReplaySubject *replaySubject = [RACReplaySubject subject];
// 2.发送信号
[replaySubject sendNext:@1];
[replaySubject sendNext:@2];
// 3.订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到的数据%@",x);
}];
// 订阅信号
[replaySubject subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到的数据%@",x);
}];
RACReplaySubject底层实现:
- 1.调用
sendNext
发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock
。 - 2.调用
subscribeNex
t订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
3.1 RAC之集合(RACTuple、RACSequence[快速遍历数组/字典])
RACTuple:元组类,类似NSArray
,用来包装值.
RACSequence:RAC中的集合类,用于代替NSArray
,NSDictionary
,可以使用它来快速遍历数组和字典。
使用场景:字典转模型
eg:
//1.遍历数组
NSArray *arr = @[@"1",@"2",@"3"];
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:arr];
// RACSequence RAC中的集合类,
//用于替代NSArray/NSDictionary 可以用它来遍历数组和字典 —— RAC常用
// RACSequence *sequence = arr.rac_sequence;
// //把集合转换成信号
// RACSignal *signal = sequence.signal;
// //订阅集合信号,内部会自动遍历所有元素发出来
// [signal subscribeNext:^(id _Nullable x) {
// NSLog(@"%@",x);
// }];
//将上面的代码简化 遍历数组
// 这里其实是三步
// 第一步: 把数组转换成集合RACSequence numbers.rac_sequence
// 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
// 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
[arr.rac_sequence.signal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
NSLog(@"%@",tuple[2]); //3
//2.遍历字典(遍历出来的键值对会包装成RACTuple(元组对象))
NSDictionary *dic = @{@"account":@"flower",@"age": @"18"};
//遍历字典
[dic.rac_sequence.signal subscribeNext:^(id _Nullable x) {
// NSString *key = x[0];
// NSString *value = x[1];
// NSLog(@"%@ %@",key,value);
//RACTupleUnpack: 用来解析元组
//宏里面的参数,传需要解析出来的变量名
// = 右边,放需要解析的元组
//等价于上面2行
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"%@ %@",key,value);
}];
//3.字典转模型
//3.1 OC写法
NSDictionary *dict = [NSDictionary readLocalFileWithName:@"test"];
NSArray *dictArr = dict[@"data"];
NSMutableArray *items = [NSMutableArray array];
for (NSDictionary *dic in dictArr) {
RACBasicKnowledgeModel *item = [RACBasicKnowledgeModel modelWithDic:dic];
[items addObject:item];
}
//3.2RAC写法
NSMutableArray *lists = [NSMutableArray array];
[dictArr.rac_sequence.signal subscribeNext:^(id _Nullable x) {
RACBasicKnowledgeModel *model = [RACBasicKnowledgeModel modelWithDic:x];
[lists addObject:model];
}];
NSLog(@"lists: %@",lists);
//3.3 RAC高级写法
//会把集合中所有的元素都映射成一个新的对象
NSArray *lists1 = [[dictArr.rac_sequence map:^id _Nullable(NSDictionary * _Nullable value) {
//value 就是集合中的元素
//id 返回对象就是映射的值
return [RACBasicKnowledgeModel modelWithDic:value];
}] array];
NSLog(@"lists1: %@",lists1);
3.2 常见使用场景(代替代理/kvo/通知/监听事件/监听文本框文字改变/rac_liftSelector<处理当界面多个请求时,需要都获取数据时,才能展示界面>)
代替代理:
-
rac_signalForSelector:
用于替代代理
代替KVO :
rac_valuesAndChangesForKeyPath:
用于监听某个对象的属性改变
监听事件:
rac_signalForControlEvents:
用于监听某个事件
代替通知:
rac_addObserverForName:
用于监听某个通知
监听文本框文字改变:
rac_textSignal:
只要文本框发出改变就会发出这个信号。
处理当界面有多次请求时,需要都获取到数据时,才能展示界面
rac_liftSelector:withSignalsFromArray:Signals:
当传入的Signals
(信号数组),每一个signal
都至少sendNext
过一次,就会去触发第一个selector
参数的方法。
使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。必须跟数组的方法一一对应,否则会奔溃 Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Wrong number of signals for updateUI (expected 0, got 2)'
eg:
// 1.代替代理
// 需求:自定义redView,监听红色view中按钮点击
// 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
// rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
// 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"点击红色按钮");
}];
// 2.KVO
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 3.监听事件
// 把按钮点击事件转换为信号,点击按钮,就会发送信号
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮被点击了");
}];
// 4.代替通知
// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
// 5.监听文本框的文字改变
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改变了%@",x);
}];
// 6.处理多个请求,都返回结果的时候,统一做处理.
RACSignal *request1 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"发送请求1"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@"发送请求2"];
[subscriber sendCompleted];
return nil;
}];
// 使用注意:几个信号,参数一的方法就几个参数,每个参数对应信号发出的数据。
[self rac_liftSelector:@selector(updateUIWithR1:r2:) withSignalsFromArray:@[request1,request2]];
}
// 更新UI
- (void)updateUIWithR1:(id)data r2:(id)data1
{
NSLog(@"更新UI%@ %@",data,data1);
}
3.3 常见宏
RAC(TARGET, [KEYPATH, [NIL_VALUE]]):
用于给某个对象的某个属性绑定。
eg:
// 只要文本框文字改变,就会修改label的文字
RAC(self.accountLabel,text) = _textField.rac_textSignal;
RACObserve(self, name):
监听某个对象的某个属性,返回的是信号。
eg:
[RACObserve(self.view, center) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
@weakify(Obj)和@strongify(Obj)
,一般两个都是配套使用,解决循环引用问题
eg:
@weakify(self);
[RACObserve(self.payButton, selected) subscribeNext:^(id _Nullable x) {
@strongify(self);
self.payButton.backgroundColor = [x boolValue] ?[UIColor colorWithHex:0x0165B8F] : [UIColor whiteColor];
}];
RACTuplePack:
把数据包装成RACTuple(元组类)
eg:
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@10,@20);
RACTupleUnpack:
把RACTuple(元组类)解包成对应的数据
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@"age",@20);
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
// name = @"age" age = @20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
3.4 RACMulticastConnection
RACMulticastConnection:用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block
,造成副作用,可以使用这个类处理。
使用注意:RACMulticastConnection
通过RACSignal
的-publish
或者-muticast:
方法创建.
RACMulticastConnection使用步骤:
- 1.创建信号
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe
- 2.创建连接
RACMulticastConnection *connect = [signal publish];
- 3.订阅信号,注意:订阅的不在是之前的信号,而是连接的信号。
[connect.signal subscribeNext:nextBlock]
- 4.连接
[connect connect]
eg:
// 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
// 解决每次订阅不要都请求数据(或者说不管订阅多少次) 只要请求一次 每次订阅只要拿到数据
//1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@"aaaa"];
return nil;
}];
NSLog(@"======不使用RACMulticastConnection=====");
//如果不使用RACMulticastConnection的话 订阅几次就发送请求几次
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"接收数据1:%@",x);
}];
[signal subscribeNext:^(id _Nullable x) {
NSLog(@"接收数据2:%@",x);
}];
NSLog(@"======使用RACMulticastConnection=====");
// //2.把信号转换成连接类
RACMulticastConnection *connection = [signal publish];
// RACMulticastConnection *connection = [signal multicast:[RACReplaySubject subject]]];
//3.订阅连接类的信号
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"接收数据1:%@",x);
}];
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"接收数据2:%@",x);
}];
//4.连接
[connection connect];
图片.png
RACMulticastConnection底层实现:
- 1.创建
connect
,connect.sourceSignal -> RACSignal
(原始信号)connect.signal -> RACSubject
- 2.订阅
connect.signal
,会调用RACSubject
的subscribeNext
,创建订阅者,而且把订阅者保存起来,不会执行block。 - 3.
[connect connect]
内部会订阅RACSignal
(原始信号),并且订阅者是RACSubject
- 3.1.订阅原始信号,就会调用原始信号中的didSubscribe
- 3.2
didSubscribe
,拿到订阅者调用sendNext
,其实是调用RACSubject
的sendNext
- 4.
RACSubject
的sendNext
,会遍历RACSubject
所有订阅者发送信号。- 4.1 因为刚刚第二步,都是在订阅
RACSubject
,因此会拿到第二步所有的订阅者,调用他们的nextBlock
- 4.1 因为刚刚第二步,都是在订阅
3.5 RACCommand
RACCommand:RAC
中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。
使用场景:监听按钮点击,网络请求
设计思想内部
signalBlock为什么要返回一个信号,这个信号有什么用
- 1.在RAC开发中,通常会把网络请求封装到
RACCommand
,直接执行某个RACCommand
就能发送请求。 - 2.当
RACCommand
内部请求到数据的时候,需要把请求的数据传递给外界,这时候就需要通过signalBlock
返回的信号传递了。
如何拿到RACCommand中返回信号发出的数据
- 1.
RACCommand
有个执行信号源executionSignals
,这个是signal of signals
(信号的信号),意思是信号发出的数据是信号,不是普通的类型。 - 2.订阅
executionSignals
就能拿到RACCommand
中返回的信号,然后订阅signalBlock
返回的信号,就能获取发出的值。
使用步骤 - 1.
signalBlock
必须要返回一个信号,不能传nil
. - 2.如果不想要传递信号,直接创建空的信号
[RACSignal empty]
; - 3.
RACCommand
中信号如果数据传递完,必须调用[subscriber sendCompleted]
,这时命令才会执行完毕,否则永远处于执行中。
使用注意
- 1.
signalBlock
必须要返回一个信号,不能传nil
. - 2.如果不想要传递信号,直接创建空的信号
[RACSignal empty]
; - 3.
RACCommand
中信号如果数据传递完,必须调用[subscriber sendCompleted]
,这时命令才会执行完毕,否则永远处于执行中。
eg:
// 1.创建命令
//RACCommand不能返回一个空的信号
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//input:执行命令传入的参数
//block调用:只要执行命令的时候就会调用
NSLog(@"input:%@======",input);
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//发送数据
[subscriber sendNext:@"123"];
//当命令内部发送数据完成,一定要主动发送完成
//注意:数据传递完,最好调用sendCompleted,这时命令才执行完毕。
[subscriber sendCompleted];
NSLog(@"subscriber:%@======",subscriber);
return nil;
}];
}];
//如何拿到命令中产生的数据
//订阅命令内部的信号
//方式一:直接订阅执行命令返回的信号
// RACSignal *signal = [command execute:@"abc"];
// [signal subscribeNext:^(id _Nullable x) {
// NSLog(@"接收到数据1:%@",x);
// }];
//方式二:
//executionSignals:信号源,信号中的信号,发送数据就是信号
//executionSignals使用注意:必须要在执行命令前订阅
[command.executionSignals subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据2:%@",x);
}];
//方式三: 开发者常用
//switchToLatest:获取最新发送的信号,只能用于信号中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据3:%@",x);
}];
//监听事件有没有完成
[command.executing subscribeNext:^(NSNumber * _Nullable x) {
if([x boolValue] == YES){ //当前正在执行
NSLog(@"正在执行");
}else{
NSLog(@"执行完成/没有执行"); //执行完成/没有执行
}
}];
// [command execute:nil];
[command execute:@"abc"];
// //创建信号中的信号
// RACSubject *signalOfSignals = [RACSubject subject];
// RACSubject *signal = [RACSubject subject];
//
// //订阅信号
// [signalOfSignals subscribeNext:^(RACSignal *x) {
//
// [x subscribeNext:^(id _Nullable x) {
//
// NSLog(@"RACSubject接收到数据 %@",x);
//
// }];
// }];
//
// //发送信号
// [signalOfSignals sendNext:signal];
// [signal sendNext:@"哈哈哈"];
// RAC高级用法
// switchToLatest:用于signal of signals,获取signal of signals发出的最新信号,也就是可以直接拿到RACCommand中的信号
//switchToLatest:获取最新发送的信号,只能用于信号中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 5.监听命令是否执行完毕,默认会来一次,可以直接跳过,skip表示跳过第一次信号。
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
// 正在执行
NSLog(@"正在执行");
}else{
NSLog(@"执行完成");
}
}];
图片.png
3.6 RAC核心 (绑定信号) 开发中很少用bind (了解一下即可)
-
操作的核心方法是
bind
(绑定),给RAC
中的信号进行绑定,只要信号一发送数据,就能监听到,从而把发送数据改成自己想要的数据。 -
在开发中很少使用
bind
方法,bind
属于RAC
中的底层方法,RAC
已经封装了很多好用的其他方法,底层都是调用bind
,用法比bind
简单.
bind方法使用步骤:
- 1、传入一个返回值
RACStreamBindBlock
的block
。 - 2、描述一个
RACStreamBindBlock
类型的bindBlock
作为block
的返回值。 - 3、描述一个返回结果的信号,作为
bindBlock
的返回值。
*注意 : 在bindBlock
中做信号结果的处理。
底层实现:
- 1、源信号调用
bind
,会重新创建一个绑定信号。 - 2、当绑定信号被订阅,就会调用绑定信号中的
didSubscribe
,生成一个bindingBlock
。 - 3、当源信号有内容发出,就会把内容传递到
bindingBlock
处理,调用bindingBlock(value,stop)
- 4、调用
bindingBlock(value,stop)
,会返回一个内容处理完成的信号(RACReturnSignal
)。 - 5、订阅
RACReturnSignal
,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
eg:
//1.创建信号
RACSubject *subject = [RACSubject subject];
//2.绑定信号
//^RACSignalBindBlock)(ValueType _Nullable value, BOOL *stop
RACSignal *bindSignal = [subject bind:^RACSignalBindBlock{ //此处的block不做事情
return ^RACSignal *(id value, BOOL *stop){
//block调用:只要源信号(指当前的subject)发送数据,就会调用block
//block的作用:处理源信号内容
//value:源信号发送的内容
NSLog(@"value: %@",value);
value = [NSString stringWithFormat:@"name:%@",value];
//返回信号
return [RACReturnSignal return:value];
};
}];
//3.订阅绑定信号
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到绑定信号处理完的信号: %@",x);
}];
//4.发送数据
[subject sendNext:@"张三"];
3.7 RAC 操作方法之映射(flattenMap,Map)
*实际项目中map常用
-
flattenMap
,Map
用于把源信号内容映射成新的内容
使用场景:监听文本框的内容改变,把结构重新映射成一个新值.
flattenMap作用:把源信号的内容映射成一个新的信号,信号可以是任意类型。
flattenMap使用步骤:
- 1.传入一个
block
,block
类型是返回值RACStream
,参数value
- 2.参数
value
就是源信号的内容,拿到源信号的内容做处理 - 3.包装成
RACReturnSignal
信号,返回出去。
flattenMap底层实现:
- 1.
flattenMap
内部调用bind
方法实现的,flattenMap
中block
的返回值,会作为bind
中bindBlock的返回值。 - 2.当订阅绑定信号,就会生成
bindBlock
。 - 3.当源信号发送内容,就会调用
bindBlock(value, *stop)
- 4.调用
bindBlock
,内部就会调用flattenMap的block
,flattenMap
的block
作用:就是把处理好的数据包装成信号。 - 5.返回的信号最终会作为
bindBlock
中的返回信号,当做bindBlock
的返回信号。 - 6.订阅
bindBlock
的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
Map作用:把源信号的值映射成一个新的值
Map使用步骤:
- 1.传入一个
block
,类型是返回对象,参数是value
- 2.value就是源信号的内容,直接拿到源信号的内容做处理
- 3.把处理好的内容,直接返回就好了,不用包装成信号,返回的值,就是映射的值。
Map底层实现:
- 1.
Map
底层其实是调用flatternMap
,Map
中block
中的返回的值会作为flatternMap中block中的值。 - 2.当订阅绑定信号,就会生成
bindBlock
。 - 3.当源信号发送内容,就会调用bindBlock(value, *stop)
- 4.调用
bindBlock
,内部就会调用flattenMap
的block
- 5.
flattenMap
的block
内部会调用Map
中的block
,把Map
中的block
返回的内容包装成返回的信号。 - 6.返回的信号最终会作为
bindBlock
中的返回信号,当做bindBlock
的返回信号。 - 7.订阅
bindBlock
的返回信号,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
flattenMap/Map简单使用
//需求: @"flowerflower" => @"name:flowerflower"
//创建信号
RACSubject *subject = [RACSubject subject];
//方式一:
// //绑定信号
RACSignal *flattenMapBindSignal = [subject flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
//block调用:只要源信号发送内容就会调用
//value:就是源信号发送内容
value = [NSString stringWithFormat:@"name:%@",value];
//返回信号 用来包装成修改内容的值
return [RACReturnSignal return:value];
}];
//方式二
RACSignal *mapBindSignal = [subject map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"name:%@",value];
}];
//订阅信号
[flattenMapBindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"flatten接收到的数据 %@",x);
}];
[mapBindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"map接收到的数据 %@",x);
}];
//发送数据
[subject sendNext:@"flowerflower"];
FlatternMap和Map的区别
- 1.
FlatternMap
中的Block
返回信号 - 2.
Map
中的Block
返回对象 - 3.开发中,如果信号发出的值不是信号,映射一般使用
Map
- 4.开发中,如果信号发出的值是信号,映射一般使用
FlatternMap
。
3.8 RAC 组合(concat
、then
、merge
)
concat:按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
concat底层实现:
- 1、当拼接信号被订阅,就会调用拼接信号的
didSubscribe
- 2、
didSubscribe
中,会先订阅第一个源信号(signalA) - 3、会执行第一个源信号(signalA)的
didSubscribe
- 4、第一个源信号(signalA)
didSubscribe
中发送值,就会调用第一个源信号(signalA)订阅者的nextBlock,通过拼接信号的订阅者把值发送出来. - 5、第一个源信号(signalA)
didSubscribe
中发送完成,就会调用第一个源信号(signalA)订阅者的completedBlock
,订阅第二个源信号(signalB)这时候才激活(signalB)。 - 6、订阅第二个源信号(signalB),执行第二个源信号(signalB)的
didSubscribe
- 7、第二个源信号(signalA)
didSubscribe
中发送值,就会通过拼接信号的订阅者把值发送出来.
eg:
//需求: 上部分:热门模块(A请求) 上部分:推荐模块(B请求) 从上往下开始展示数据
//一般都在signal内部发送请求,只要用到请求,一般都是用RACSignal做的
RACSignal *signalA = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"开始请求热门模块数据");
[subscriber sendNext:@"我是热门模块的数据"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *signalB = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"开始请求推荐模块数据");
[subscriber sendNext:@"我是推荐模块的数据"];
[subscriber sendCompleted];
return nil;
}];
//concat:按顺序去连接
//使用注意:concat,第一个信号必须要调用sendCompleted
//创建组合信号(把signalA拼接到signalB后面,signalA发送完成后,signalB才能被激活)
RACSignal *concatSignal = [signalA concat:signalB];
//订阅组合信号
[concatSignal subscribeNext:^(id _Nullable x) {
//既能拿到A请求的数据,又能拿到B请求的数据
NSLog(@"接收到数据 %@",x);
}];
图片.png
then:用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
then底层实现:
- 1.先过滤掉之前的信号发出的值。
- 2.使用
concat
连接then
返回的信号
//then:用于连接两个信号,当第一个信号完成,才会返回then返回的信号 (开发者几乎没有这样的需求)
//忽然第一个信号所有值
//创建组合信号
RACSignal *thenSignal = [signalA then:^RACSignal * _Nonnull{
//返回信号就是需要组合的信号
return signalB;
}];
// 只能接收到第二个信号的值,也就是then返回信号的值
[thenSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
}];
图片.png
merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用.
merge底层实现:
- 1.合并信号被订阅的时候,就会遍历所有信号,并且发出这些信号。
- 2.每发出一个信号,这个信号就会被订阅
- 3.也就是合并信号一被订阅,就会订阅里面所有的信号。
- 4.只要有一个信号被发出就会被监听。
// 合并信号,任何一个信号发送数据,都能监听到.
RACSignal *mergeSignal = [signalA merge:signalB];
//任何一个信号发送内容都来走这个block
[mergeSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
}];
图片.png
zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
zipWith底层实现:
- 1.定义压缩信号,内部就会自动订阅
signalA
,signalB
- 2.每当
signalA
或者signalB
发出信号,就会判断signalA
,signalB
有没有发出个信号,有就会把最近发出的信号都包装成元组发出。
//zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件
//zipWith:等所有信号都发送内容的时候才会调用
//开发场景:当一个界面多个请求的时候,要等所有请求都请求完了才显示UI
RACSignal *zipSignal = [signalA zipWith:signalB];
[zipSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
}];
图片.png
combineLatest:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal
至少都有过一次sendNext
,才会触发合并的信号。
combineLatest底层实现:
- 1.当组合信号被订阅,内部会自动订阅
signalA
,signalB
,必须两个信号都发出内容,才会被触发。 - 2.并且把两个信号组合成元组发出。
//combineLatestWith:将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号
//一般用于类似登录逻辑的情况
//把两个信号合并成一个信号,和zip一样,没什么区别 (打印结果和zip一样)
RACSignal *combSignal = [signalA combineLatestWith:signalB];
[combSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
}];
reduce聚合:用于信号发出的内容是元组,把信号发出元组的值聚合成一个值
reduce底层实现:
- 订阅聚合信号,每次有内容发出,就会执行
reduceblcok
,把信号内容转换成reduceblcok
返回的值。
//reduce:聚合,用于信号发出的内容都是元组,把信号发出元组的值聚合成一个值
//需求:登录界面常用 账号 && 密码 同时有值 登录按钮才能点击
RACSignal *comSignal = [RACSignal combineLatest:@[self.accountText.rac_textSignal,self.pwdText.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
//block:只要源信号发送内容就会调用,组成新的一个值
//聚合的值,就是组合信号的内容
return @(account.length && pwd.length);
}];
//订阅信号
[comSignal subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
self.loginBtn.backgroundColor = [x boolValue] ? [UIColor colorWithHex:0x0165B8] : [UIColor colorWithHex:0x0165B8 alpha:0.5];
self.loginBtn.enabled = [x boolValue];
}];
//RAC(self.loginBtn,enabled) = comSignal 等价于
/**
[comSignal subscribeNext:^(id _Nullable x) {
self.loginBtn.enabled = [x boolValue];
}];
*/
// RAC(self.loginBtn,enabled) = comSignal;
3.9 RAC 核心操作方法过滤
filter:过滤信号,使用它可以获取满足条件的信号.
// 过滤:
// 每次信号发出,会先执行过滤条件判断.
[[self.accountText.rac_textSignal filter:^BOOL(NSString * _Nullable value) {
//value:就是源信号的值
//返回值 就是过滤条件,只有满足这个条件,才能获取到内容
return [value length] > 5;
}] subscribeNext:^(NSString * _Nullable x) {
NSLog(@"接收到数据 %@",x);
}] ;
ignore:忽略完某些值的信号.
// 内部调用filter过滤,忽略掉ignore的值
//当输入的值为11时,则不会打印。
[[self.accountText.rac_textSignal ignore:@"11"] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
// 过滤,当上一次和当前的值不一样,就会发出内容。
// 在开发中,刷新UI经常使用,只有两次数据不一样才需要刷新
RACSubject *subject =[RACSubject subject];
// distinctUntilChanged:如果当前的值跟上一个值相同,就不会被订阅到
[[subject distinctUntilChanged] subscribeNext:^(id _Nullable x) {
NSLog(@"接收到数据 %@",x);
}];
[subject sendNext:@"aaa"];
[subject sendNext:@"aaa"];
[subject sendNext:@"bbb"];
图片.png
take:从开始一共取N次的信号
// 1、创建信号
RACSubject *signal = [RACSubject subject];
// 2、处理信号,订阅信号
//从开始一共取N次的信号 (取前面几个值)
[[signal take:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 3.发送信号
[signal sendNext:@"aaa"];
[signal sendNext:@"bbb"];
[signal sendNext:@"ccc"];
图片.png
takeLast:取最后N次的信号,前提条件,订阅者必须调用完成,因为只有完成,就知道总共有多少信号.
// 1、创建信号
RACSubject *signal = [RACSubject subject];
// 2、处理信号,订阅信号
[[signal takeLast:1] subscribeNext:^(id x) {
NSLog(@"%@",x); //打印:ccc
}];
// 3.发送信号
[signal sendNext:@"aaa"];
[signal sendNext:@"bbb"];
[signal sendNext:@"ccc"];
[signal sendCompleted];
*takeUntil:(RACSignal ):获取信号直到执行完这个信号
// 监听文本框的改变,直到当前对象被销毁
[self.textField.rac_textSignal takeUntil:self.rac_willDeallocSignal];
skip:(NSUInteger):跳过几个信号,不接受。 不了解
switchToLatest:用于signalOfSignals
(信号的信号),有时候信号也会发出信号,会在signalOfSignals
中,获取signalOfSignals
发送的最新信号。 不了解
以下作为冷门知识点,仅供了解(开发中几乎不用)
操作方法之秩序
doNext:
执行Next
之前,会先执行这个Block
doCompleted:
执行sendCompleted
之前,会先执行这个Block
[[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
[subscriber sendCompleted];
return nil;
}] doNext:^(id x) {
// 执行[subscriber sendNext:@1];之前会调用这个Block
NSLog(@"doNext");;
}] doCompleted:^{
// 执行[subscriber sendCompleted];之前会调用这个Block
NSLog(@"doCompleted");;
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
操作方法之线程
-
deliverOn: 内容传递切换到制定线程中,副作用在原来线程中,把在创建信号时block中的代码称之为副作用。
-
subscribeOn: 内容传递和副作用都会切换到制定线程中。
操作方法之时间
timeout:
超时,可以让一个信号在一定的时间后,自动报错。
RACSignal *signal = [[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
return nil;
}] timeout:1 onScheduler:[RACScheduler currentScheduler]];
[signal subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
// 1秒后会自动调用
NSLog(@"%@",error);
}];
interval 定时:每隔一段时间发出信号
[[RACSignal interval:1 onScheduler:[RACScheduler currentScheduler]] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
delay:
延迟发送next
。
RACSignal *signal = [[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
[subscriber sendNext:@1];
return nil;
}] delay:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
操作方法之重复
retry重试 :
只要失败,就会重新执行创建信号中的block,直到成功.
__block int i = 0;
[[[RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
if (i == 10) {
[subscriber sendNext:@1];
}else{
NSLog(@"接收到错误");
[subscriber sendError:nil];
}
i++;
return nil;
}] retry] subscribeNext:^(id x) {
NSLog(@"%@",x);
} error:^(NSError *error) {
}];
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);
}];
throttle节流:
当某个信号发送比较频繁时,可以使用节流,在某一段时间不发送信号内容,过了一段时间获取信号的最新内容发出。
RACSubject *signal = [RACSubject subject];
_signal = signal;
// 节流,在一定时间(1秒)内,不接收任何信号内容,过了这个时间(1秒)获取最后发送的信号内容发出。
[[signal throttle:1] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];