首先介绍一下什么是RAC
ReactiveCocoa(简称为RAC),是由Github开源的一个应用于iOS和OS开发的新框架,Cocoa是苹果整套框架的简称,因此很多苹果框架喜欢以Cocoa结尾。
然后介绍一下RAC的作用,以及有哪些功能
在我们iOS开发过程中,当某些事件响应的时候,需要处理某些业务逻辑,这些事件都用不同的方式来处理。 比如按钮的点击使用action,ScrollView滚动使用delegate,属性值改变使用KVO等系统提供的方式。 其实这些事件,都可以通过RAC处理 ReactiveCocoa为事件提供了很多处理方法,而且利用RAC处理事件很方便,可以把要处理的事情,和监听的事情的代码放在一起,这样非常方便我们管理,就不需要跳到对应的方法里。非常符合我们开发中高聚合,低耦合的思想。
在这里说道RAC那就必须得说一下编程思想这个词了
编程思想的由来
:在开发中我们会遇见各种各样的需求,经常会思考如何快速的完成这些需求,这样就会慢慢形成快速完成这些需求的思想。
先简单介绍下目前咱们已知的编程思想。
1、面向过程
:处理事情以过程为核心,一步一步的实现。
2、面向对象
:万物皆对象
3、链式编程思想
:是将多个操作(多行代码)通过点号(.)链接在一起成为一句代码,使代码可读性好。a(1).b(2).c(3)
链式编程特点:方法的返回值是block,block必须有返回值(本身对象),block参数(需要操作的值)
代表:masonry框架。
4、 响应式编程思想
:不需要考虑调用顺序,只需要知道考虑结果,类似于蝴蝶效应,产生一个事件,会影响很多东西,这些事件像流一样的传播出去,然后影响结果,借用面向对象的一句话,万物皆是流。
代表:KVO运用。
练习二:KVO底层实现。
5、函数式编程思想
:是把操作尽量写成一系列嵌套的函数或者方法调用。
函数式编程本质:就是往方法中传入Block,方法中嵌套Block调用,把代码聚合起来管理
函数式编程特点:每个方法必须有返回值(本身对象),把函数或者Block当做参数,block参数(需要操作的值)block返回值(操作结果)
代表:ReactiveCocoa。
练习三:用函数式编程实现,写一个加法计算器,并且加法计算器自带判断是否等于某个值.
#######4.ReactiveCocoa编程思想
ReactiveCocoa结合了几种编程风格
:
函数式编程
(Functional Programming)
响应式编程
(Reactive Programming)
所以,你可能听说过ReactiveCocoa被描述为函数响应式编程
(FRP
)框架。
以后使用RAC解决问题,就不需要考虑调用顺序,直接考虑结果,把每一次操作都写成一系列嵌套的方法中,使代码高聚合,方便管理。
#######如何导入ReactiveCocoa框架
使用CocoaPods(用于管理第三方框架的插件)帮助我们导入。
具体步骤:PS:CocoaPods教程(http://code4app.com/article/cocoapods-install-usage)
podfile如果只描述pod 'ReactiveCocoa', '~> 4.0.2-alpha-1',会导入不成功
报错信息:会提示我们说Swift什么什么玩意的。然后我们需要打开一句话,use_frameworks
因为RAC需要用到Swift的一些东西
下面会具体介绍一下RAC中的一些类的基本使用
1.首先介绍RACSignal这个类 使用流程是 1创建信号
,2订阅者订阅信号
,3发送信号
RACSignal信号类!当数据改变时,信号内部接收到数据,就会马上发出数据,但是RACSignal本身是不具有发送消息的功能,而是交给内部一个叫做信号订阅者发出的!(冷信号
)
创建完以后必须要去订阅信号!只有订阅者把信号订阅以后冷信号才会变热信号!
代码实现
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// didSubscribe调用:只要一个信号被订阅就会调用
// didSubscribe作用:发送数据
NSLog(@"信号被订阅");
// 3.发送数据
[subscriber sendNext:@1];
return nil;
}];
// 2.订阅信号(热信号)
[signal subscribeNext:^(id x) {
// nextBlock调用:只要订阅者发送数据就会调用
// nextBlock作用:处理数据,展示到UI上面
// x:信号发送的内容
NSLog(@"%@",x);
}];
// 只要订阅者调用sendNext,就会执行nextBlock
// 只要订阅RACDynamicSignal,就会执行didSubscribe
// 前提条件是RACDynamicSignal,不同类型信号的订阅,处理订阅的事情不一样
下面介绍一下RACSignal的底层实现原理
第一步
RACSignal在创建的时候会带有一个Block这个Block什么是不被调用呢!看下一步!
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// didSubscribe调用:只要一个信号被订阅就会调用
// didSubscribe作用:发送数据
NSLog(@"信号被订阅");
#第三步在这里
// 3.发送数据
[subscriber sendNext:@1];
发送数据的底层都帮我们做了什么呢?
看了源代码发现,在我们发送这个信号的时候RAC内部会调用订阅者去发送数据!
return nil;
}];
然后我们进行下一步,去订阅刚刚创建的这个信号!
代码如下:
[signal subscribeNext:^(id x) {
// nextBlock调用:只要订阅者发送数据就会调用
// nextBlock作用:处理数据,展示到UI上面
// x:信号发送的内容
NSLog(@"%@",x);
}];
#######但是在订阅的工程中我们并没有去创建订阅者,我们只是调用了一下这个subscribeNext方法,这个方法内部帮我们做了很多事!比如说,在我们调用这个方法的时候,RAC内部会帮我们创建一个订阅者然后去订阅我们上一步创建的冷信号,在订阅后,冷信号随之变成热信号!
最后一步,看第一步中Block内!!!
上面是RACSignal
的基本使用方法!
上面我们大致说来说RACSignal
这个类怎么使用!
但是在RACSignal
第一步中的Block室友返回值的可是当时我是直接返回了一个nil
;现在说一下具体返回一个什么玩意!
首先我们还需要看一下RACSignal
的创建方法
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// didSubscribe调用:只要一个信号被订阅就会调用
// didSubscribe作用:发送数据
NSLog(@"信号被订阅");
// 3.发送数据
[subscriber sendNext:@1];
return nil;
}];
经过观察我们不难发现RACSignal
这个类后面的Block返回值是RACDisposable
类型的返回值,那么我们就需要返回一个这样类型的返回值!
但是需要注意的是:就算我们不去释放/销毁信号,那么信号也会知道销毁!
我们试着自己去释放这个信号:代码如下
RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id subscriber) {
// didSubscribe调用:只要一个信号被订阅就会调用
// didSubscribe作用:发送数据
NSLog(@"信号被订阅");
// 3.发送数据
[subscriber sendNext:@1];
return [[RACDisposable disposableWithBlock:^{
// block调用时刻:当信号发送完成或者发送错误,就会自动执行这个block,取消订阅信号。
// 执行完Block后,当前信号就不在被订阅了。
NSLog(@"信号被销毁");
}];];
}];
#######通过RACDisposable 调用disposableWithBlock进行信号的销毁!
下面说一下一个新的类!RACSubject
这个类,以及具体的实现过程
RACSubject
:信号提供者:通常会替换代理!
特点
:既可以创建信号,又可以发送信号!
具体使用如下!
第一步:创建信号!
// 1.创建信号
RACSubject *subject = [RACSubject subject];//直接通过类方法创建信号!
第二部:订阅信号!
// 不同信号订阅的方式不一样
// RACSubject处理订阅:仅仅是保存订阅者
[subject subscribeNext:^(id x) {
NSLog(@"订阅者一接收到数据:%@",x);
}];
第三部:发送信号!
// 3.发送数据
[subject sendNext:@1];// 这里是自己发送数据
具体的实现过程:
第一步中创建信号的时候:RACSubject会在内部创建一个可变数组,用来接收订阅者,当订阅者订阅信号的时候回把订阅者放到这个数组中!
第二部中当订阅者去订阅信号的时候会把创建的订阅者存放到第一步创建的数组中!
第三部中当subject发送数据的时候,会遍历第一步中的数组,然后取出所有的订阅者然后去发送数据!
这就是RACSubject这个类的基本使用!下面会具体介绍这个类如何作为代理去进行使用!
下面要介绍的是RACSubject的一个子类,RACReplaySubject这个类(重复提供信号类)用法基本和上面差不离!o
第一步:创建信号!
RACReplaySubject *subject = [RACReplaySubject subject];
第二部:订阅信号!
[subject subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
第三部:发送信号!
[subject sendNext:@1];
具体的实现过程:(跟它的父类相比,这个类比父类更牛13);
第一步中创建信号的时候:RACSubject会在内部创建一个可变数组,用来接收订阅者,当订阅者订阅信号的时候回把订阅者放到这个数组中!
(和父类没什么两样)
第二部中当订阅者去订阅信号的时候会把要发送的值存放到第一步创建的数组中!,并且在这个一步就开始遍历数组中所有的值!
第三部中当subject发送数据的时候,会遍历第一步中的数组,然后取出所有的订阅者然后去发送数据!
这个和它父类唯一一个不一样的地方在于 这个类可以在信号被订阅之前发送数据,儿RACSubject 却不行,这就是区别!!
下面来说一下这个RACSubject如何替换代理!
拿一个简答的例子来说吧!
我们自己自定义了一个View,View上添加了一个Button,然后我们需要点击按钮通知控制器去做一些事情!
如果是原本的OC方法
,我么需要写一整套的代理协议
,如果使用RACSubject
这个类的话,我们只需要在Button的点击方法中创建一个信号
,然后在控制器中去订阅这个信号
就OK了,就是这个简单!
具体代码如下
// 需求:
// 1.给当前控制器添加一个按钮,modal到另一个控制器界面
// 2.另一个控制器view中有个按钮,点击按钮,通知当前控制器
步骤一:在第二个控制器.h,添加一个RACSubject代替代理。
@interface TwoViewController : UIViewController
@property (nonatomic, strong) RACSubject *delegateSignal;
@end
步骤二:监听第二个控制器按钮点击
@implementation TwoViewController
- (IBAction)notice:(id)sender {
// 通知第一个控制器,告诉它,按钮被点了
// 通知代理
// 判断代理信号是否有值
if (self.delegateSignal) {
// 有值,才需要通知
[self.delegateSignal sendNext:nil];
}
}
@end
步骤三:在第一个控制器中,监听跳转按钮,给第二个控制器的代理信号赋值,并且监听.
@implementation OneViewController
- (IBAction)btnClick:(id)sender {
// 创建第二个控制器
TwoViewController *twoVc = [[TwoViewController alloc] init];
// 设置代理信号
twoVc.delegateSignal = [RACSubject subject];
// 订阅代理信号
[twoVc.delegateSignal subscribeNext:^(id x) {
NSLog(@"点击了通知按钮");
}];
// 跳转到第二个控制器
[self presentViewController:twoVc animated:YES completion:nil];
}
@end
下面介绍一下RAC中的集合类!
首先需要了解的是RACTuple这个类!这个叫做元组
,但不是Swift
中的元组
,因为Swift中的元组可以存放基本数据类型
,而RACTuple``不可以存放基本数据类型
,只能存放OC对象
,如果要存放基本数据类型需要进行包装
下面主要介绍的是如何对RACTuple进行遍历(数组)
废话不多说,上代码
主要思想就是,把数组转换为一个信号,然后去订阅这个信号,去执行一些操作!
RACSequence :就是RAC中的集合了,相当于OC中的数组和字典!,这是个信号类
//创建一个数组
NSArray *arr = @[@"213",@"321",@1];
// 转换成RAC集合
// RACSequence *sequence = arr.rac_sequence;
// 订阅集合信号,内部会自动遍历所有的元素发出来
// [signal subscribeNext:^(id x) {
// NSLog(@"%@",x);
// }];
// 上面是具体的方法。
下面是直接通过链式编程直接就可以出来
[arr.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
下面是字典的遍历
直接上代码
这里需要解释一下:RACTupleUnpack 的含义,这是一个宏,定义的非常深,确实是没有看明白,但是也不妨碍我们去使用, RACTupleUnpack:用来解析元组,宏里面的参数,传需要解析出来的变量名,= 右边,放需要解析的元组
// 字典
NSDictionary *dict = @{@"account":@"aaa",@"name":@"xmg",@"age":@18};
// 转换成集合
[dict.rac_sequence.signal subscribeNext:^(RACTuple *x) {
// NSString *key = x[0];
// NSString *value = x[1];
// NSLog(@"%@ %@",key,value);
// RACTupleUnpack:用来解析元组
// 宏里面的参数,传需要解析出来的变量名
// = 右边,放需要解析的元组
RACTupleUnpack(NSString *key,NSString *value) = x;
NSLog(@"%@ %@",key,value);
}];
上面的都是铺垫,下面介绍一个比较重要的,也是我们比较关心的———字典转模型,通过刚刚的方法去进行字典转模型
下面把一些主要的代码写一下!
.h中的代码
首先我们需要,一个Plist文件或者是JSON文件
首先我们创建一个模型,创建两个属性
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSString *icon;
//然后来个类方法!
+ (instancetype)flagWithDict:(NSDictionary *)dict;
.m中的代码
//通过KVC进行赋值
+ (instancetype)flagWithDict:(NSDictionary *)dict
{
Flag *flag = [[self alloc] init];
[flag setValuesForKeysWithDictionary:dict];
return flag;
}
下面是Controller中的代码
//下面以Plist文件为准
// 解析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];
}];//上面是我们预想的方法,虽然很方便,但是还是不够牛13;
下面介绍一种比较叼的方法
// 高级用法
// 会把集合中所有元素都映射成一个新的对象
NSArray *arr = [[dictArr.rac_sequence map:^id(NSDictionary *value) {
// value:集合中元素
// id:返回对象就是映射的值
return [Flag flagWithDict:value];
}] array];
NSLog(@"%@",arr);
//面试的时候可以深深的装个13.
下面我们接着介绍,RAC中的一些常用的方法!
RAC中另一种方法去代替delegate
代码如下:
详解:这个方法是用来监听某个方法实发触发的!!!
2.代替代理
// 1.代替代理:1.RACSubject 2.rac_signalForSelector
// 只要传值,就必须使用RACSubject
[[_redView rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id x) {
NSLog(@"控制器知道按钮被点击");
}];
// RAC:
// 把控制器调用didReceiveMemoryWarning转换成信号
// rac_signalForSelector:监听某对象有没有调用某方法
// [[self rac_signalForSelector:@selector(didReceiveMemoryWarning)] subscribeNext:^(id x) {
// NSLog(@"控制器调用didReceiveMemoryWarning");
// }];
2.代替KVO
去监测一个View的Frame是否改变
[_redView rac_observeKeyPath:@"frame" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
//
}];
//如果我们要在一个Controller中监听多个值得改变的情况下我们可以直接日如下写!
[[_redView rac_valuesForKeyPath:@"frame" observer:nil] subscribeNext:^(id x) {
// x:修改的值
NSLog(@"%@",x);
}];
[_redView rac_observeKeyPath:@"bounds" options:NSKeyValueObservingOptionNew observer:nil block:^(id value, NSDictionary *change, BOOL causedByDealloc, BOOL affectedOnlyLastComponent) {
//
}];
3.监听事件
[[_btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@"按钮点击了");
}];
4.代替通知
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) {
NSLog(@"%@",x);
}];
5.监听文本框
[_textField.rac_textSignal subscribeNext:^(id x) {
NSLog(@"%@",x);
}];