一. ReactiveCocoa简介
ReactiveCocoa(简称为RAC
),是由Github
开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
- ReactiveCocoa作用
在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。 比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。
其实这些事件,都可以通过RAC处理。 ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中
高聚合,低耦合
的思想。
- ReactiveCocoa编程思想
在开发中我们也不能太依赖于某个框架,否则这个框架不更新了,导致项目后期没办法维护,比如之前Facebook提供的Three20框架
,在当时也是神器,但是后来不更新了,也就没什么人用了。因此我感觉学习一个框架,还是有必要了解它的编程思想
。编程思想的由来
:在开发中我们会遇见各种各样的需求,经常会思考如何快速的完成这些需求,这样就会慢慢形成快速完成这些需求的思想。
- 先简单介绍下目前咱们已知的编程思想。
-
面向过程
:处理事情以过程为核心,一步一步的实现。 -
面向对象
:万物皆对象 -
链式编程思想
:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
- 链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表
:masonry框架。
// 创建红色view
UIView *redView = [[UIView alloc] init];
redView.backgroundColor = [UIColor redColor];
// 设置约束,一定要先把view添加上去,才能设置约束
[self.view addSubview:redView];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
// 链式编程思想特点:方法返回值必须要方法调用者
// block:把需要操作的值当做block参数,block也需要返回值,就是方法调用者
// 给make添加left,top约束(保存在make数组),调用equalTo给这两个约束赋值(该值保存在约束)
make.left.top.equalTo(@10);
make.right.bottom.equalTo(@-10);
}];
/*
mas_makeConstraints执行流程:
1.创建约束制造者MASConstraintMaker,绑定控件,生成了一个保存所有约束的数组
2.执行mas_makeConstraints传入进行的block
3.让约束制造者安装约束
* 1.清空之前的所有约束
* 2.遍历约束数组,一个一个安装
*/
-
响应式编程思想
:
不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。代表
:KVO运用。
// 监听方法本质:并不需要修改方法的实现,仅仅想判断下有没有调用
//`KVO的本质`:就是监听一个对象有没有调用set方法
@interface Person : NSObject
{
@public
NSString *_name;
}
/** */
@property (nonatomic, strong) NSString *name;
@end
Person *p = [[Person alloc] init];
[p addObserver:self forKeyPath:@"name" options:NSKeyValueObservingOptionNew context:nil];
//通过此种方式修改成员变量KVO无法监听
_p -> _name = [NSString stringWithFormat:@"%d",i];
//KVO的底层实现
// 1.苹果内部自定义NSKVONotifying_Person(Person类的子类)
// 2.修改当前对象p的isa指针,指向子类([NSKVONotifying_Person class]),并保存观察者。
// 3.重写这个类的set方法,在其中找到并通知观察者。(在内部恢复父类做法)
//@implementation NSObject (KVO)
- (void)x_addObserver:(NSObject *)observer forKeyPath:(NSString *)keyPath options:(NSKeyValueObservingOptions)options context:(void *)context
{
// 把观察者保存到当前对象
objc_setAssociatedObject(self, (__bridge const void *)(observerKey), observer, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
// 修改对象isa指针
object_setClass(self, [XMGKVONotifying_Person class]);
}
//@implementation XMGKVONotifying_Person
- (void)setName:(NSString *)name
{
[super setName:name];
id obsetver = objc_getAssociatedObject(self, observerKey);
[obsetver observeValueForKeyPath:@"name" ofObject:self change:nil context:nil];
}
-
函数式编程思想
:是把操作尽量写成一系列嵌套的函数或者方法调用。
- 函数式编程本质: 就是往方法中传入Block,方法中嵌套Block调用,把代码聚合起来管理
- 函数式编程特点: 每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表
:ReactiveCocoa。
CalculateManager *mgr = [[CalculateManager alloc] init];
int result = [[mgr calculate:^(int result){
// 存放所有的计算代码
result += 5;
result *= 5;
return result;
}] result];
NSLog(@"%d",result);
- ReactiveCocoa编程思想
ReactiveCocoa结合了几种编程风格:函数式编程(Functional Programming)
,响应式编程(Reactive Programming)
。所以,你可能听说过ReactiveCocoa被描述为函数响应式编程(FRP
)框架。以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
二. ReactiveCocoa使用
先要搞清楚框架中常用的类,在RAC中最核心的类RACSiganl,搞定这个类就能用ReactiveCocoa开发了。
1. RACSiganl
- 信号类(RACSiganl),只是表示当数据改变时,信号内部会发出数据,它本身不具备发送信号的能力,而是交给内部一个订阅者去发出。
- 默认一个信号都是冷信号,也就是值改变了,也不会触发,只有订阅了这个信号,这个信号才会变为热信号,值改变了才会触发
//RACSubscriber:表示订阅者的意思,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。通过create创建的信号,都有一个订阅者,帮助他发送数据
// 1.创建信号(冷信号)
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// block调用时刻:每当有订阅者订阅信号,就会调用block。
//3.订阅者发送数据
[subscriber sendNext: @1];
return nil;
}];
// 2.订阅信号(热信号) // 只要订阅RACDynamicSignal,就会执行1中block
[signal subscribeNext:^(id _Nullable x) {
// nextBlock调用:只要订阅者发送数据就会调用
NSLog(@"---%@",x);
}];
RACSignal底层实现:
1.创建信号:首先把block保存到创建的信号RACDynamicSignal中,此时为冷信号,还不会触发。
2.信号被订阅:也就是调用signal的subscribeNext:nextBlock
- subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
- subscribeNext内部会调用siganl中1保存的block。
3.发送数据:siganl保存的block中调用[subscriber sendNext:@1]; sendNext底层其实就是执行subscriber的nextBlock
2. RACDisposable
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber ) {
self->_subscriber = subscriber;//3若订阅者存在,则不会自动取消信号订阅
// 3.发送信号
[subscriber sendNext:@"123"];// 1默认一个信号发送数据完毕们就会主动取消订阅.
return [RACDisposable disposableWithBlock:^{
NSLog(@"信号被取消订阅了");// 2只要信号取消订阅就会来这
}];
}];
// 2.订阅信号
RACDisposable *disposable = [signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[disposable dispose];// 4取消订阅信号(订阅者一直存在的情况)
3. RACSubject:继承RACSignal
信号提供者,自己可以充当信号,又能发送信号。通常用来代替代理。
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.订阅信号 不同信号订阅的方式不一样
// RACSubject处理订阅:仅仅是保存订阅者
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"订阅者一接收到数据:%@",x);
}];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"订阅者二接收到数据:%@",x);
}];
// 3.发送数据 底层实现:遍历所有的订阅者,调用订阅者下保存的block如上
[subject sendNext:@1];
4. RACReplaySubject :继承RACSubject
重复提供信号类,RACReplaySubject可以先发送数据,在订阅信号,RACSubject就不可以
//跟RACSiganl不一样,创建信号时没有block
RACReplaySubject *subject = [RACReplaySubject subject];
// 遍历所有的值,创建一个订阅者去发送数据
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"订阅者1======%@",x);
}];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"订阅者2======%@",x);
}];
//1.保存值 2.遍历所有的订阅者,发送数据
[subject sendNext:@1];
[subject sendNext:@2];
[subject subscribeNext:^(id _Nullable x) {
NSLog(@"订阅者3 ======%@",x);
}];
// 2019-06-03 16:03:37.114715+0800 RAC[66683:1975972] 订阅者1======1
// 2019-06-03 16:03:37.114889+0800 RAC[66683:1975972] 订阅者2======1
// 2019-06-03 16:03:37.114971+0800 RAC[66683:1975972] 订阅者1======2
// 2019-06-03 16:03:37.115065+0800 RAC[66683:1975972] 订阅者2======2
// 2019-06-03 16:03:37.120068+0800 RAC[66683:1975972] 订阅者3 ======1
// 2019-06-03 16:03:37.120205+0800 RAC[66683:1975972] 订阅者3 ======2
// 2019-06-03 16:03:37.120321+0800 RAC[66683:1975972] 订阅者4 ======1
// 2019-06-03 16:03:37.120403+0800 RAC[66683:1975972] 订阅者4 ======2
// RACReplaySubject:可以先发送信号,在订阅信号.结果大体一致:
5.RACTuple:元组类,类似NSArray,用来包装值.
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"213",@"321",@1]];
NSString *str = tuple[0];
NSLog(@"%@",str);
6. RACSequence:RAC中的集合类
用于代替NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
// 1.遍历数组
NSArray *numbers = @[@1,@2,@3,@4];
// 这里其实是三步
// 第一步: 把数组转换成集合RACSequence numbers.rac_sequence
// 第二步: 把集合RACSequence转换RACSignal信号类,numbers.rac_sequence.signal
// 第三步: 订阅信号,激活信号,会自动把集合中的所有值,遍历出来。
[numbers.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2.遍历字典,遍历出来的键值对会包装成RACTuple(元组对象)
NSDictionary *dict = @{@"name":@"xmg",@"age":@18};
// rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// 解包元组,会把元组的值,按顺序给参数里面的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
//相当于 NSString *key = x[0]; NSString *value = x[1];
NSLog(@"%@ %@",key,value);
}];
//3. 解析plist文件
NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
// NSMutableArray *arr = [NSMutableArray array];
// [dictArr.rac_sequence.signal subscribeNext:^(NSDictionary *x) {
// Flag *flag = [Flag flagWithDict:x];
// [arr addObject:flag];
// }];
// 高级用法,会把集合中所有元素都映射成一个新的对象
// map:映射的意思,目的:把原始值value映射成一个新值 array: 把集合转换成数组
// 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。
NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
// value:集合中元素 id:返回对象就是映射的值
return [Flag flagWithDict:value];
}] array];
NSLog(@"%@",arr);
7. RACMulticastConnection
用于当一个信号,被多次订阅时,为了保证创建信号时,避免多次调用创建信号中的block,造成副作用,可以使用这个类处理。使用注意:RACMulticastConnection通过RACSignal的-publish或者-muticast:方法创建.
// RACMulticastConnection:解决重复请求问题
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"发送数据");//connect连接的时候
[subscriber sendNext:@1];
return nil;
}];
// 创建连接,确定源信号的订阅者RACSubject
RACMulticastConnection *connection = [signal publish];
// 注意:订阅信号,也不能激活信号,只是保存订阅者到数组,必须通过连接,当调用连接,就会一次性调用所有订阅者的sendNext:
[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);
}];
[connection connect];
//2019-06-04 13:57:53.900856+0800 RAC[74055:2113698] 发送数据
//2019-06-04 13:57:53.901065+0800 RAC[74055:2113698] 1----1
//2019-06-04 13:57:53.901157+0800 RAC[74055:2113698] 2----1
//2019-06-04 13:57:53.901254+0800 RAC[74055:2113698] 3----1
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
8. RACCommand
RAC中用于处理事件的类,可以把事件如何处理,事件中的数据如何传递,包装到这个类中,他可以很方便的监控事件的执行过程。使用场景
:监听按钮点击,网络请求
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal * _Nonnull(id _Nullable input) {
//input传入的参数,不可返回空信号,[RACSignal empty]也可,执行命令调用该block
NSLog(@"input====%@",input);//1
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:@"执行命令产生的数据"];
[subscriber sendCompleted];//发送数据完成,一定主动发送完成结束命令执行状态
return nil;
}];
}];
// //底层RACReplaySubject
// RACSignal *signal = [command execute:@1];
// [signal subscribeNext:^(id _Nullable x) {
// NSLog(@"111%@",x);
// }];
// //executionSignals:信号源,信号中信号,signalOfSignals:信号:发送数据就是信号
// //必须在执行命令前订阅
// [command.executionSignals subscribeNext:^(id _Nullable x) {
// [x subscribeNext:^(id _Nullable x) {
// NSLog(@"%@",x);
// }];
// }];
// [command execute:@1];
//switchToLatest:获取信号中信号发送的最新信号
[command.executionSignals.switchToLatest subscribeNext:^(id _Nullable x) {
NSLog(@"====%@",x);
}];
[command execute:@1];
// 监听事件有没有完成
[command.executing subscribeNext:^(id x) {
if ([x boolValue] == YES)
{
NSLog(@"当前正在执行");
}
else
{
NSLog(@"执行完成/没有执行");
}
}];//[command.executing skip:1] subscribeNext//跳过第一步没有执行阶段
- switchToLatest实现流程
// 创建信号中信号
RACSubject *signalOfSignals = [RACSubject subject];
RACSubject *signalA = [RACSubject subject];
// 订阅信号
// [signalOfSignals subscribeNext:^(RACSignal *x) {
// [x subscribeNext:^(id x) {
// NSLog(@"%@",x);
// }];
// }];
// switchToLatest:获取信号中信号发送的最新信号
[signalOfSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 发送信号
[signalOfSignals sendNext:signalA];
[signalA sendNext:@1];
9. RAC核心操作方法bind
- 所有的信号(RACSignal)都可以进行操作处理,因为所有操作方法都定义在RACStream.h中,而RACSignal继承RACStream。
- 运用的是Hook(钩子)思想,Hook是一种用于改变API(应用程序编程接口:方法)执行结果的技术
- RAC开发方式:RAC中核心开发方式,也是绑定,之前的开发方式是赋值,而用RAC开发,应该把重心放在绑定,也就是可以在创建一个对象的时候,就绑定好以后想要做的事情,而不是等赋值之后在去做事情。
- ReactiveCocoa操作的核心方法是bind(绑定),给RAC中的信号进行绑定,只要信号一发送数据,就能监听到,从而把发送数据改成自己想要的数据。
- 在开发中很少使用bind方法,bind属于RAC中的底层方法,RAC已经封装了很多好用的其他方法,底层都是调用bind,用法比bind简单.
// 1.创建信号
RACSubject *subject = [RACSubject subject];
// 2.绑定信号
// 参数一(value):表示接收到信号的原始值,还没做处理
// 参数二(*stop):用来控制绑定Block,如果*stop = yes,那么就会结束绑定。
// 返回值:信号,做好处理,在通过这个信号返回出去,一般使用RACReturnSignal,需要手动导入头文件RACReturnSignal.h。
RACSignal *bindSignal = [subject bind:^RACSignalBindBlock _Nonnull{
// block调用时刻:只要绑定信号被订阅就会调用
return ^RACSignal *(id value, BOOL *stop){
// block调用:只要源信号发送数据,就会调用block
// 返回信号,不能传nil,返回空信号[RACSignal empty]
NSLog(@"value:%@",value);
return [RACReturnSignal return:@"456"];
};
}];
// 3.订阅绑定信号
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"123"];
// 2019-06-05 17:49:26.338623+0800 RAC[80890:2328714] value:123
// 2019-06-05 17:49:26.338809+0800 RAC[80890:2328714] 456
-
底层实现:
1.源信号调用bind,会重新创建一个绑定信号。
2.当绑定信号被订阅,就会调用绑定信号中的didSubscribe,生成一个bindingBlock。
3.当源信号有内容发出,就会把内容传递到bindingBlock处理,调用bindingBlock(value,stop)
4.调用bindingBlock(value,stop),会返回一个内容处理完成的信号(RACReturnSignal)。
5.订阅RACReturnSignal,就会拿到绑定信号的订阅者,把处理完成的信号内容发送出来。
10.RAC操作方法之映射:flattenMap,Map
如果信号发出的值是信号,映射一般使用FlatternMap。
如果信号发出的值不是信号,映射一般使用Map。
- flattenMap:把源信号的内容映射成一个新的信号,信号可以是任意类型。
//底层bind实现
RACSubject *subject = [RACSubject subject];
RACSignal *bindSignal = [subject flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
//block:只要源信号发送数据就会执行
//value:源信号发送的内容 返回信号用来包装修改内容值
return [RACReturnSignal return:value];
}];
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"123"];
flattenMap实现:信号中信号取值
RACSubject *signalOfsignals = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
RACSignal *bindSignal = [signalOfsignals flattenMap:^__kindof RACSignal * _Nullable(id _Nullable value) {
return value;//源信号发送的内容signal
}];
[bindSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 发送信号
[signalOfsignals sendNext:signal];
[signal sendNext:@"213"];
- map:把源信号的值映射成一个新的值
RACSubject *subject = [RACSubject subject];
RACSignal *bindSignal = [subject map:^id _Nullable(id _Nullable value) {
return [NSString stringWithFormat:@"000:%@",value];
}];
[bindSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"123"];
11. ReactiveCocoa操作方法之组合。
- concat: 按一定顺序拼接信号,当多个信号发出的时候,有顺序的接收信号。
RACSignal *siganlA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送上部分请求");
[subscriber sendNext:@"上部分数据"];
[subscriber sendCompleted];
return nil;
}];
RACSignal *siganlB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送下部分请求");
[subscriber sendNext:@"下部分数据"];
return nil;
}];
// concat:按顺序去连接,第一个信号必须要调用sendCompleted
// 创建组合信号
RACSignal *concatSignal = [siganlA concat:siganlB];
// 订阅组合信号
[concatSignal subscribeNext:^(id x) {
// 既能拿到A信号的值,又能拿到B信号的值
NSLog(@"%@",x);
}];
- then: 用于连接两个信号,当第一个信号完成,才会连接then返回的信号。
RACSignal *siganlA = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送上部分请求");
[subscriber sendNext:@"上部分数据"];//被忽略
[subscriber sendCompleted];
return nil;
}];
RACSignal *siganlB = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSLog(@"发送下部分请求");
[subscriber sendNext:@"下部分数据"];
return nil;
}];
//创建组合信号 then:忽悠掉第一个信号所有值
RACSignal *thenSiganl = [siganlA then:^RACSignal *{
// 返回信号就是需要组合的信号
return siganlB;
}];
// 订阅信号
[thenSiganl subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2019-06-06 14:32:43.158779+0800 RAC[84544:2449482] 发送上部分请求
// 2019-06-06 14:32:43.159032+0800 RAC[84544:2449482] 发送下部分请求
// 2019-06-06 14:32:43.159205+0800 RAC[84544:2449482] 下部分数据
- merge:把多个信号合并为一个信号,任何一个信号有新值的时候就会调用.
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 组合信号
RACSignal *mergeSiganl = [signalA merge:signalB];
// 订阅信号
[mergeSiganl subscribeNext:^(id x) {
// 任意一个信号发送内容都会来这个block
NSLog(@"%@",x);
}];
// 发送数据
[signalB sendNext:@"下部分"];
[signalA sendNext:@"上部分"];
// 2019-06-06 14:36:13.145844+0800 RAC[84608:2451306] 下部分
// 2019-06-06 14:36:13.145975+0800 RAC[84608:2451306] 上部分
- zipWith:把两个信号压缩成一个信号,只有当两个信号同时发出信号内容时,并且把两个信号的内容合并成一个元组,才会触发压缩流的next事件。
// zipWith:夫妻关系
RACSubject *signalA = [RACSubject subject];
RACSubject *signalB = [RACSubject subject];
// 压缩成一个信号
// zipWith:当一个界面多个请求的时候,要等所有请求完成才能更新UI
// zipWith:等所有信号都发送内容的时候才会调用
RACSignal *zipSignal = [signalA zipWith:signalB];
// 订阅信号
[zipSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 发送信号
[signalB sendNext:@2];
[signalA sendNext:@1];
// <RACTwoTuple: 0x6000023ef260> (1, 2)
- combineLatest: 将多个信号合并起来,并且拿到各个信号的最新的值,必须每个合并的signal至少都有过一次sendNext,才会触发合并的信号。
//reduce:聚合 reduceBlock参数:跟组合的信号有关,一一对应
RACSignal *comineSiganl = [RACSignal combineLatest:@[_accountFiled.rac_textSignal,_pwdField.rac_textSignal] reduce:^id(NSString *account,NSString *pwd){
// block:只要源信号发送内容就会调用,组合成新一个值
NSLog(@"%@ %@",account,pwd);
// 聚合的值就是组合信号的内容
return @(account.length && pwd.length);
}];
//订阅组合信号
[comineSiganl subscribeNext:^(id x) {
_loginBtn.enabled = [x boolValue];
}];
// RAC(_loginBtn,enabled) = comineSiganl;
12.ReactiveCocoa操作方法之过滤
- filter:过滤信号,使用它可以获取满足条件的信号.
// 只有当我们文本框的内容长度大于5,才想要获取文本框的内容
[[_textField.rac_textSignal filter:^BOOL(id value) {
// value:源信号的内容
return [value length] > 5;
// 返回值,就是过滤条件,只有满足这个条件,才能能获取到内容
}] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
- ignore:忽略完某些值的信号.
//ignore:忽略一些值 ignoreValues:忽略所有的值
RACSubject *subject = [RACSubject subject];
//RACSignal *ignoreSignal = [subject ignoreValues];
RACSignal *ignoreSignal = [subject ignore:@"1"];
// 订阅信号
[ignoreSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//发送数据
[subject sendNext:@"13"];//与1不等可以拿到
- take:从开始一共取N次的信号
RACSubject *subject = [RACSubject subject];
RACSubject *signal = [RACSubject subject];
//take:取前面几个值 takeLast:取后面多少个值.必须要发送完成
//takeUntil:只要传入信号发送完成或者发送任意数据,就不能在接收源信号的内容takeLast:2
[[subject takeUntil:signal] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[signal sendCompleted];// [signal sendNext:@1];
[subject sendNext:@"3"];
- distinctUntilChanged:当上一次的值和当前的值有明显的变化就会发出信号,否则会被忽略掉。
RACSubject *subject = [RACSubject subject];
[[subject distinctUntilChanged] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"1"];
[subject sendNext:@"2"];//输出1 2
- skip:(NSUInteger):跳过几个信号,不接受。
RACSubject *subject = [RACSubject subject];
[[subject skip:2] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
[subject sendNext:@"1"];
[subject sendNext:@"2"];
[subject sendNext:@"3"];//3
13. RAC常见宏
- RAC(TARGET, [KEYPATH, [NIL_VALUE]])
用于给某个对象的某个属性绑定信号,只要信号产生内容,就把内容给属性赋值。
// 只要文本框文字改变,就会修改label的文字
RAC(self.label,text) = _textField.rac_textSignal;
- RACObserve(self, name):监听某个对象的某个属性,返回的是信号。
[RACObserve(self.view, frame) subscribeNext:^(id _Nullable x) {
NSLog(@"%@",x);
}];
- @weakify(Obj)和@strongify(Obj),一般两个都是配套使用,解决循环引用问题.
- RACTuplePack:把数据包装成RACTuple(元组类)
RACTupleUnpack:把RACTuple(元组类)解包成对应的数据。
// 把参数中的数据包装成元组
RACTuple *tuple = RACTuplePack(@10,@20);
NSLog(@"%@",tuple[0]);//10
// name = @"10" age = 20
RACTupleUnpack(NSString *name,NSNumber *age) = tuple;
NSLog(@"%@, %@",name, age);
14. RAC常见用法
- 代替代理:rac_signalForSelector;需要传值:RACSubject
// 需求:自定义redView,监听红色view中按钮点击
// 之前都是需要通过代理监听,给红色View添加一个代理属性,点击按钮的时候,通知代理做事情
// rac_signalForSelector:把调用某个对象的方法的信息转换成信号,就要调用这个方法,就会发送信号。
// 这里表示只要redV调用btnClick:,就会发出信号,订阅就好了。
[[redV rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"点击红色按钮");
}];
- 代替KVO
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
//rac_valuesForKeyPath
//rac_observeKeyPath//block内处理
- 监听事件
// 把按钮点击事件转换为信号,点击按钮,就会发送信号
[[self.btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮被点击了");
}];
- 代替通知
// 4.代替通知
// 把监听到的通知转换信号
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
- 监听文本框
// 监听文本框的文字改变
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"文字改变了%@",x);
}];
- 所有信号都发送数据后再执行方法:rac_liftSelector
- (void)testRAC_liftSelector
{
//保证全部请求完成,再搭建界面
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//请求数据AFN
NSLog(@"信号1请求");
[subscriber sendNext:@"信号1数据"];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
//请求数据AFN
NSLog(@"信号2请求");
[subscriber sendNext:@"信号2数据"];
return nil;
}];
//当数组中所有信号都发送数据的时候,才会执行selector
//方法的参数必须跟数组的信号一一对应,参数就是每个信号发送的数据
[self rac_liftSelector:@selector(updateUI::) withSignalsFromArray:@[signal1, signal2]];
}
- (void)updateUI:(NSString *)string1 :(NSString *)string2
{
NSLog(@"%@====%@",string1, string2);
}