序
之前就一直在看ReactiveCocoa 相关知识 但是有感觉每次看完收货甚微,且感觉零零散散 ,本blog 仅仅是记录自己的学习过程
文章目录
- 1 ReactiveCocoa 介绍
- 2 ReactiveCocoa的应用场景
- 3 ReactiveCocoa 被描述为函数式响应编程(FRP)框架
- 4 如何添加ReactiveCocoa
- 5 分析ReactiveCocoa中的常见类
- 5-1 RACSignal:信号类
- 5-2 RACSubscriber:订阅者
- 5-3 RACDisposable:取消订阅
- 5-4 RACSubject:信号提供者可发信号可充当信号
- 5-5 RACTuple:元祖类 类似NSArray用来包装值
- 5-6 RACSequence (Sequence:序列排序) :RAC中的集合类 用于代替NSArray 和 NSDictionary 可以使用它来快速遍历数组和字典
- 5-7 RACCommand :RAC中用于处理事件的类, 可以吧事件如何处理,事件中的数据如何传递,包装到这个类中 他可以很方便的监控事件的执行过程
- 5-8 RACMulticastConnection 用于当一个信号被多次订阅是为了保证创建信号时 避免多次调用创建信号中的block 造成副作用 可以使用这个类创建
- 5-9 RACScheduler :RAC中的队列 用GCD封装的
- 5-10 RACUnit :表示Stream不包含有意义的值 也就是看到这个 可以直接理解为nil
- 6 ReactiveCocoa 开发中常见的用法
- 6-1 代替代理
- 6-2 代替KVO
- 6-3 监听事件
- 6-4 代替通知
- 6-5 监听文本框文字的改变
- 6-6 处理多个请求 都返回结果的时候统一做处理
- 6-7 定时器
- 7 ReactiveCocoa 开发中常见的用法
- 7-1 RAC(TARGET, ...) 用于给某个对象的某个属性绑定
- 7-2 RACObserve(TARGET, KEYPATH) 监听某个对象的某个属性 返回的是信号
- 7-3 RACTuplePack 把数据包装成RACTuple (元祖类) RACTupleUnpack 把元祖解包成对应的数据
1 ReactiveCocoa 介绍
ReactiveCocoa(其简称为RAC)是由Github 开源的一个应用于iOS和OS X开发的新框架。RAC具有函数式编程和响应式编程的特性
2 ReactiveCocoa的应用场景
对于一个应用来说,绝大部分的时间都是在等待某些事件的发生或响应某些状态的变化,比如用户的触摸事件、应用进入后台、网络请求成功刷新界面等等,而维护这些状态的变化,常常会使代码变得非常复杂,难以扩展。而 ReactiveCocoa 给出了一种非常好的解决方案,它使用信号来代表这些异步事件,提供了一种统一的方式来处理所有异步的行为,包括代理方法、block 回调、target-action 机制、通知、KVO 等
3 ReactiveCocoa 被描述为函数式响应编程(FRP)框架
ReactiveCocoa结合了几种编程风格
函数式编程(Functional Programming)
是把操作尽量写成一系列嵌套的函数或者方法调用。
函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表: ReactiveCocoa
响应式编程(Reactive Programming )
不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
代表:KVO运用
链式编程
是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表作 Masonry
4 添加ReactiveCocoa到项目中
使用cocoapods
具体电脑配置cocoapods
网上资料一大堆 我就不多说了 自行Google
pod 'ReactiveCocoa', '~> 2.5'
5 分析ReactiveCocoa的中的常见类
仔细看了源码 发现 RAC中最核心的类是RACSignal, 好了 接下来就是研究RACSignal了
5-1 RACSignal:信号类
一般表示将来有数据传递 只要有数据改变信号内部接受到数据 就会马上发出数据
注意:
RACSignal(信号类) 只是表示数据改变时 信号内部会发出数据 它本身不具备发送信号的能力 而是交给内部一个订阅者去发出(下文会讲到RACSubject类 有区别)
默然信号都是冷信号 也就是值改变了也不会触发 只有订阅了这个信号 这个信号才是热信号 值改变了才会触发
订阅信号:调用信号RACSignal的subscribeNext就能订阅
RACSignal的简单使用:
// 1 创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 2 发送信号
[subscriber sendNext:@"123"];
// 如果不发送数据 最好发送信号完成 内部会自动调用[RACDisposable disposable]取消订阅信号。
[subscriber sendCompleted];
return [RACDisposable disposableWithBlock:^{
//block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁了");
}];
}];
// 3 订阅信号
[signal subscribeNext:^(id x) {
// block 调用时刻 每当有信号发出数据 就会调用block;
NSLog(@"接收到数据%@",x);
}];
- RACSignal的底层实现原理
- 1.创建信号,首先把didSubscribe保存到信号中,还不会触发。
- 2.当信号被订阅,也就是调用signal subscribeNext:nextBlock
- 2.2 subscribeNext内部会创建订阅者subscriber,并且把nextBlock保存到subscriber中。
- 2.1 subscribeNext内部会调用siganl的didSubscribe
- 3.siganl的didSubscribe中调用[subscriber sendNext:@1];
- 3.1 sendNext底层其实就是执行subscriber的nextBlock
5-2 RACSubscriber
表示订阅者的意思 用于发送信号 这是一个协议 不是一个类 只要遵守这个协议 并且实现方法才能成为订阅者 通过create创建的信号都有一个订阅者 帮助他发送数据
5-3 RACDisposable
用于取消订阅或者清理资源 当信号发送完成或者发送错误的时候 就会自动触发他
- 不想监听某个信号时 可以通过它主动取消订阅信号
5-4 RACSubject
信号提供者 自己可以充当信号 又能发送信号
- 通常用来代替代理 有了它 就不必要定义代理了
RACReplySubject 重复提供信号类 ,RACSubject的子类
RACReplySubject 和RACSubject区别
RACReplySubject 可以先发送信号再订阅信号, RACSubject就不可以
使用场景1 :如果一个信号每被订阅一次 就需要把之前的值重复发送一遍 使用重复提供信号类
使用场景2 :可以设置capacity数量来限制缓存的value的数量 即只缓存最新的几个值
RACSubject 的简单实用
// 1 创建信号
RACSubject *subject = [RACSubject subject];
// 2 订阅信号
[subject subscribeNext:^(id x) {
NSLog(@"第一个订阅者 %@",x);
}];
[subject subscribeNext:^(id x) {
NSLog(@"第二个订阅者 %@",x);
}];
// 3 发送信号
[subject sendNext:@123];
- RACSubject的底层实现原理
- RACSubject底层实现 和RACSignal不一样
- 1 调用SubjectNext 订阅信号 只是把订阅者保存起来 并且订阅者的nextBlock已经赋值了
- 2 调用sendNext 发送信号遍历刚刚保存的订阅者 一个一个调用订阅者的nextBlock
RACReplaySubject 的简单使用
// 1 创建信号
RACReplaySubject *replySubject = [RACReplaySubject subject];
// 2 发送信号
[replySubject sendNext:@"我是ReplySubject -- 1"];
[replySubject sendNext:@"我是ReplySubject -- 2"];
// 3 订阅信号
[replySubject subscribeNext:^(id x) {
NSLog(@"第一个订阅者接收到的数据%@",x);
}];
[replySubject subscribeNext:^(id x) {
NSLog(@"第二个订阅者接收到的数据%@",x);
}];
// 下边是打印结果
第一个订阅者接收到的数据我是ReplySubject -- 1
第一个订阅者接收到的数据我是ReplySubject -- 2
第二个订阅者接收到的数据我是ReplySubject -- 1
第二个订阅者接收到的数据我是ReplySubject -- 2
-
RACReplySubject的底层实现原理
- RACReplaySubject:底层实现和RACSubject不一样。
- 1.调用sendNext发送信号,把值保存起来,然后遍历刚刚保存的所有订阅者,一个一个调用订阅者的nextBlock。
- 2.调用subscribeNext订阅信号,遍历保存的所有值,一个一个调用订阅者的nextBlock
- 如果想当一个信号被订阅,就重复播放之前所有值,需要先发送信号,在订阅信号。 也就是先保存值,在订阅值。
- RACSubject 的项目中的使用 替换代理
// 在第一个控制器里边 我用的是脱线跳转 所有在此方法中获取控制器 实例化subject 然后订阅信号
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
DetailViewController *detailVC = segue.destinationViewController;
// 实例化
detailVC.delagateSignal = [RACSubject subject];
// 订阅信号
[detailVC.delagateSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
}
// 在DetailViewController.h 中添加属性
@property(nonatomic, strong)RACSubject *delagateSignal;
// 在DetailViewController.m 中的点击方法中 判断 delagateSignal 是否实例话 然后发送信号
- (IBAction)delegateBtn:(id)sender {
if (self.delagateSignal) {
// 发送信号
[self.delagateSignal sendNext:@"我是从详情里边出来的哈哈"];
}
}
5-5 RACTuple:元祖类 类似NSArray 用来包装值
5-6 RACSequence (Sequence:序列排序) :RAC中的集合类 用于代替NSArray 和 NSDictionary 可以使用它来快速遍历数组和字典
- 简单使用 遍历数组 和遍历字典
// 1 遍历数组
NSArray *arr = @[@1,@2,@3,@4,@5];
[arr.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 2 遍历字典
// 2 遍历字典
NSDictionary *dict = @{@"name":@"奥卡姆剃须刀",
@"age":@"25",
@"location":@"beijing"
};
[dict.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
此处打印出来的键值会被包装为RACTuple元祖类型 以下为打印结果
Reactive Demo[9312:370525] (
location,
beijing
)
// 所以遍历字典时候 应该将id直接改为RACTuple
[dict.rac_sequence.signal subscribeNext:^(RACTuple * x) {
// 解包元祖 会把元祖的值 按顺序给参数里边的变量赋值
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"%@----%@",key,value);
}];
// 打印结果
name----奥卡姆剃须刀
age----25
location----beijing
- 实现字典转模型
// OC写法
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"PeopleList.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *modelArr = [NSMutableArray arrayWithCapacity:dictArr.count];
for (NSDictionary *dict in dictArr) {
PeopleModel *model = [PeopleModel peopleWithDict:dict];
[modelArr addObject:model];
}
// RAC 写法
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"PeopleList.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSMutableArray *modelArr = [NSMutableArray arrayWithCapacity:dictArr.count];
// RACSequence 调用subcribeNext 并不会马上执行NextBlock 而是要等一会 可以在下方添加一个nslog 会现在log
[dictArr.rac_sequence.signal subscribeNext:^(id x) {
PeopleModel *model = [PeopleModel peopleWithDict:x];
[modelArr addObject:model];
}];
NSLog(@"奥卡姆剃须刀");
// RAC高级写法
NSString *filePath = [[NSBundle mainBundle]pathForResource:@"PeopleList.plist" ofType:nil];
NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath];
NSArray *modelArr = [[dictArr.rac_sequence map:^id(id value) {
return [PeopleModel peopleWithDict:value];
}]array];
NSLog(@"%@",modelArr);
// map: 映射的意思 把原始的值value映射成一个新值 形成一个新的集合 RACDynamicSequence
// array 把RACDynamicSequence集合准换成我们熟知的数组
// 底层实现 当信号被订阅是 会遍历集合中的原始值 映射成新值 并且保存到新的RACDynamicSequence集合中
5-7 RACCommand :RAC中用于处理事件的类, 可以吧事件如何处理,事件中的数据如何传递,包装到这个类中 他可以很方便的监控事件的执行过程
- 监听按钮的点击,网络请求 简单实用
// RACCommand使用步骤
// 1 创建命令
// 2 在signalBlock中 创建信号signal 作为signalblock的返回
// 3 执行命令 execute
// 1 创建命令
RACCommand *command = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) {
NSLog(@"执行命令%@",input);
// 2 创建信号 用来传递数据
return [RACSignal createSignal:^RACDisposable *(id subscriber) {
// 在这里边发送网络请求 然后将请求道的结果数据返回给外界
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"我是数据"];
// 发送信号完成后 要发送完成
[subscriber sendCompleted];
});
return nil;
}];
}];
// 3 订阅RACCommand的信号 在此处可以拿到网络请求回来的数据
[command.executionSignals subscribeNext:^(id x) {
[x subscribeNext:^(id x) {
NSLog(@"----%@",x);
}];
}];
// RAC 的高级用法
//switchToLatest 用于signal of signals 获取 signal of signals 发出的最新信号也就是可以直接拿到RACCommand中的信号
[command.executionSignals.switchToLatest subscribeNext:^(id x) {
NSLog(@"switchToLatest---%@",x);
}];
// 4 监听命令是否执行完毕 默认会来一次 可以直接跳过 skip表示跳过信号
[[command.executing skip:1] subscribeNext:^(id x) {
if ([x boolValue] == YES) {
NSLog(@"正在执行");
}else{
NSLog(@"执行完毕");
}
}];
// 5 执行命令 执行命令的时候 会调用command的block 订阅信号时 block中的信号才能发送出去
[command execute:@1];
-
RACCommand 使用注意
- 1 signalBlock 必须返回一个信号 不能返回nil 不过可以传一个空信号 [RACSignal empty];
- 2 RACCommand 中信号如果数据传递完 必须调用 [subscriber sendCompleted];这是命令才会执行完毕 否则一直在执行过程中
- 3 comman 需要强引用
-
RACCommand的设计思想 signalBlock内部为什么要返回一个信号 这个信号有什么用呢
- 1 在RAC开发中 通常会把网络请求封装在RACCommand 直接执行某个command就能发送请求
- 2 在RACCommand内部请求道数据的时候 需要把请求的数据传递给外界这时候就需要signalblock返回的信号传递了
-
如何拿到command中返回信号发出的数据
- 1 RACCommand 有一个执行信号源executionSignals 这个是signal of signals(信号的信号),意思是信号发出的数据是信号,不是普通的类型。
- 2 订阅executionSignals 就能拿到RACCommand中返回的信号 然后订阅signalblock返回的信号 就能获取发出的值
监听当前命令是否正在执行executing
使用场景 监听按钮的点击 网络请求
5-8 RACMulticastConnection 用于当一个信号被多次订阅是为了保证创建信号时 避免多次调用创建信号中的block 造成副作用 可以使用这个类创建
- RACMulticastConnection 通过RACSignal 的publish或者muticast:方法创建
// 来先看一个例子
// 需求:假设在一个信号中发送请求,每次订阅一次都会发送请求,这样就会导致多次请求。
// 解决:使用RACMulticastConnection就能解决.
// 看一下错误的例子
// 1 创建请求信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@1];
return nil;
}];
// 2 订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"接受数据1");
}];
// 3 再次订阅信号
[signal subscribeNext:^(id x) {
NSLog(@"接收数据2");
}];
// 4 运行结果 会执行两遍发送请求 也就是每次订阅都会发送一次请求
// 下边是使用RACMulticastConnection 能够解决发送两次请求的问题
// RACMulticastConnection 解决重复请求的问题
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
NSLog(@"发送请求");
[subscriber sendNext:@1];
return nil;
}];
// 2 创建链接
RACMulticastConnection *connect = [signal publish];
// 3 订阅信号
[connect.signal subscribeNext:^(id x) {
NSLog(@"接受数据 1 --%@",x);
}];
[connect.signal subscribeNext:^(id x) {
NSLog(@"接受数据 2 -- %@",x);
}];
// 4 链接 激活信号
[connect connect];
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
5-9 RACScheduler :RAC中的队列 用GCD封装的
5-10 RACUnit :表示Stream不包含有意义的值 也就是看到这个 可以直接理解为nil
6 ReactiveCocoa 开发中常见的用法
6-1 代替代理
- 个人感觉这个方法代替代理并没有省多少代码 类似于block 代码相对集中了而已
// 代替代理
// 主控制器中的代码
CustomView *V = [[CustomView alloc]initWithFrame:CGRectMake(100, 100, 200, 200)];
V.backgroundColor = [UIColor redColor];
[self.view addSubview:V];
V.delegate = self;
[[V rac_signalForSelector:@selector(TapClick)] subscribeNext:^(id x) {
NSLog(@"点击了视图中的按钮%@",x);
}];
// CustomView 中的代码 和之前遵守协议设置代理代理实现方法是一样一样的
.h
@protocol CustomViewDelagate
- (void)CustomViewTapClick;
@end
@interface CustomView : UIView
@property (nonatomic,weak) id delegate;
@end
.m
- (void)TapClick{
if ([self.delegate respondsToSelector:@selector(CustomViewTapClick)]) {
[self.delegate CustomViewTapClick];
}
}
6-2 代替KVO
- 使用RAC代替KVO很简单,一句话就可以搞定,而且相比传统的KVO,不仅代码不用放在一起写美观了很多,同时还能达到高聚合低耦合的目标
//第一种方式 代替KVO
[RACObserve(scrollView, contentOffset) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
// 第二种方式
// 2.KVO
// 把监听redV的center属性改变转换成信号,只要值改变就会发送信号
// observer:可以传入nil
[[redV rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
6-3 监听事件
- rac_signalForControlEvents 同样简单,一句话搞定
// 监听事件
[[self.signalBtn rac_signalForControlEvents:UIControlEventTouchUpInside]subscribeNext:^(id x) {
// 传递的btn
NSLog(@"点击了按钮%@",x);
}];
6-4 代替通知
// 正确的写法--1 这个写法 当dealloc信号发出的时候 就释放通知
[[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
NSLog(@"键盘弹出");
}];
// 正确的写法--2 自己创建一个要释放的信号
RACSignal *deallocSignal = [self rac_signalForSelector:@selector(viewWillDisappear:)];
[[[[NSNotificationCenter defaultCenter]rac_addObserverForName:UIKeyboardWillShowNotification object:nil] takeUntil:deallocSignal] subscribeNext:^(id x) {
NSLog(@"键盘弹出%@",x);
}];
// 这是个错误的示范
这个写法有个问题,这样子写信号不会被释放,当你再次收到键盘弹出的通知时他会叠加上次的信号进行执行,并一直叠加下去,所以我们在用上面的写法
[[[NSNotificationCenter defaultCenter]rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
6-5 监听文本框文字的改变
[self.textFiled.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
6-6 处理多个请求 都返回结果的时候统一做处理
- (void)moreRequest{
RACSignal *request1 = [RACSignal
createSignal:^RACDisposable *(id subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"我是数据 - 1"];
[subscriber sendCompleted];
});
return nil;
}];
RACSignal *request2 = [RACSignal createSignal:^RACDisposable *(id subscriber) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[subscriber sendNext:@"我是数据 - 2"];
[subscriber sendCompleted];
});
return nil;
}];
// 前边的方法参数和后边信号的数据是一一对应的
[self rac_liftSelector:@selector(reloadData1:Data2:) withSignalsFromArray:@[request1,request2]];
}
// 接收到数据后在这里统一reloadData
- (void)reloadData1:(id)Data1 Data2:(id)Data2{
NSLog(@"ReloadData%@---%@",Data1,Data2);
}
6-7 定时器
- 延时执行
// 延时执行
[[RACScheduler mainThreadScheduler] afterDelay:2 schedule:^{
NSLog(@"2秒后执行我");
}];
- 定时执行
// 定时执行 takeUntil 要条件限制一下 否则当控制器销毁后 还是会继续执行
[[[RACSignal interval:1 onScheduler:[RACScheduler mainThreadScheduler]]takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
NSLog(@"每隔1s会执行");
}];
7 ReactiveCocoa 开发中常见的宏
7-1 RAC(TARGET, ...) 用于给某个对象的某个属性绑定
RAC(self.label,text) = self.textFiled.rac_textSignal;
7-2 RACObserve(TARGET, KEYPATH) 监听某个对象的某个属性 返回的是信号
[RACObserve(self.textFiled,center ) subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
7-3 RACTuplePack 把数据包装成RACTuple (元祖类) RACTupleUnpack 把元祖解包成对应的数据
// 把参数中的数据包装成元祖
RACTuple *tuple = RACTuplePack(@10,@20,@30);
// 要一一对应付
RACTupleUnpack(NSString *name,NSString *name2,NSString *name3) = tuple;
NSLog(@"%@---%@---%@",name,name2,name3);
本文章借鉴网络多篇文章 仅为学习笔记记录