ReactiveCocoa学习笔记
2016-11-30 16:03
15 查看
最近在学习RAC框架,断断续续的看了好久,看的文章博客可以说少说也得有一两百,所以这是一个综合的产物,也可以算是一个学习笔记,毕竟好记性不如烂笔头,记录下来,以供以后复习。如有侵权,联系立删。
书归正传:
但是一般我们在开发的时候,(仅针对iOS开发)M层一般只会写有几个成员属性,也就是说,所有的业务逻辑,网络层代码之类的,全部都写在了Controller中,这样,就造成了C层中有大量的,冗杂的代码,让人维护起来很费劲,本着‘高内聚,低耦合’的原则,有些大神们就想到了MVVM的设计模式。
MVVM其实是M-V-C-VM,也就是比正常的MVC多了一个ViewModel,
ViewModel主要从事的业务
1. 从网络请求、处理数据,然后返回给Controller,controller再传递给View层显示
2. 界面中的业务逻辑
也就是把以前Controller中的逻辑代码,换了个地方,全部写在了ViewModel中了。
所以,从根本上来说,MVC和MVVM是可以相互转换的。
下面从使用中来了解RAC。
信号分为热信号和冷信号,默认创建完成之后是冷信号,这个时候是无用的,也就是值改变了,也不会触发,只有订阅
可以看到返回了一个block,该block返回值为RACDisposable,参数为subscriber,该参数遵守RACSubscriber协议
RACDisposable,如果想要取消订阅,使用此类的方法
RACSubscriber协议中的方法有:
用来传值,或者返回数据。
该方法是
调用之后,冷信号变成热信号,
使用中要注意:
1. 在block中,如果订阅之后,不想传值,可以使用协议中的方法
2. 传值完成之后,必须调用
3. 如果返回值为ERROR,且要传递,调用
了解了信号和订阅,我们就可以开始简单的使用一下了。
假如界面中有一个textview一个label. 我们可以绑定他们两个,以达到用户在textview中输入的值,显示在label中
代码中的rac_textSignal返回的是textView的文本信号。我们订阅之后,就可以获得这个信号,其中的
比如网络请求这种,一方传递出去一个信号,需要另一方返回一个信号的这种流程。前后端通信这种就是一个很好的例子: 前端post数据给后端,然后服务器返回给我们相应的JSON字符串,这个就可以使用RACCommand来处理。
与RACSignal的区别, RACSignal是单向的,只能后者订阅前者,反之则不行
该方法只有一个 block参数,创建的时候,要在block中创建一个RACSignal,并返回,如果不想返回信号,则可以
该方法除了一个 block参数之外,还有一个enable信号,用来控制command能否执行。
只有执行了,订阅的block才会调用
executionSignals
executing
errors
这些属性的类型都是RACSignal. 目的就是在执行之后,可以订阅,查看信号内的内容。
allowsConcurrentExecution BOOL类型的变量,是否允许同时多个执行,默认是NO
双重订阅
RAC的高级用法
switchToLatest方法: 获取信号中信号最近发出信号,订阅最近发出的信号。也就是可以跳过外层的信号,直接订阅内部的信号。
或者
RACCommand的用法
订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
发送信号 sendNext:(id)value
可以在代码段里看到一个
所以我们可以使用索引来获取Tuple中的值
如果遇到NSDictionary,则RACTuple则会循环键值对的个数次,倒叙的遍历字典的键值对(通常是把一个键值对当成数组的一个元素)
我们可以使用RACTupleUnpack把tuple转换成dictionary的键值关系
上面的写法和下面的得到的结果是一样的
字典转模型
rac_signalForSelector: fromProtocol: 不可以传值
当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法
用于给某个对象的某个属性绑定。
例子
监听某个对象的某个属性,返回的是信号。
例子
另外还有一个使用RAC+MVVM模拟的登录小demo
参考资料:
学习RAC小记-适合给新手看的RAC用法总结
ReactiveCocoa(FRP)-进阶篇
被误解的 MVC 和被神化的 MVVM
ReactiveCocoa干货
书归正传:
RAC简介
RAC是一个F(functional)R(reactive)P(programming)框架,可以很好的结合MVVM. 写出的项目易于维护。但是导入MVVM和RAC的项目,学习成本有点高,如果自己独立开发,或者团队内成员水平较平均且普遍较高,那么可以尝试这么做。注意:
RAC框架已经有几年的历史了,理所当然的有OC,Swift版本,从2.5版本之后,就开始全面支持swift,不再支持OC,所以如果你的项目是OC的项目,
请使用cocoapods导入2.5或之前的版本,如果你对cocoapods不甚了解,请移步我的cocoapods踩坑:
另外需要了解的:
MVVM设计模式。
相信大家对MVC肯定都很熟悉,Model业务模型,View前端显示,Controller控制器。但是一般我们在开发的时候,(仅针对iOS开发)M层一般只会写有几个成员属性,也就是说,所有的业务逻辑,网络层代码之类的,全部都写在了Controller中,这样,就造成了C层中有大量的,冗杂的代码,让人维护起来很费劲,本着‘高内聚,低耦合’的原则,有些大神们就想到了MVVM的设计模式。
本人理解
如有不对,还请各位大神批评指正。MVVM其实是M-V-C-VM,也就是比正常的MVC多了一个ViewModel,
VM从网络请求数据,然后参照Model的格式和一些需求,处理数据,然后返回给Controller,再传递给View展示出来,大概就是图中的画出来的样子
ViewModel主要从事的业务
1. 从网络请求、处理数据,然后返回给Controller,controller再传递给View层显示
2. 界面中的业务逻辑
也就是把以前Controller中的逻辑代码,换了个地方,全部写在了ViewModel中了。
所以,从根本上来说,MVC和MVVM是可以相互转换的。
下面从使用中来了解RAC。
重要的类
RACSignal
这个是RAC中最重要的一个类了(RAC中全部都是以信号来处理的)信号分为热信号和冷信号,默认创建完成之后是冷信号,这个时候是无用的,也就是值改变了,也不会触发,只有订阅
subscribe了之后,信号才能变成热信号,订阅的block(
subscribeNext)才会被调用。
创建信号的方法:
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe;
可以看到返回了一个block,该block返回值为RACDisposable,参数为subscriber,该参数遵守RACSubscriber协议
RACDisposable,如果想要取消订阅,使用此类的方法
- (void)dispose;
RACSubscriber协议中的方法有:
- (void)sendNext:(id)value;
- (void)sendError:(NSError *)error;
- (void)sendCompleted;
用来传值,或者返回数据。
订阅信号的方法
- (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
该方法是
RACSignalsubscription类别中的一个方法。
调用之后,冷信号变成热信号,
nextBlock会执行
使用中要注意:
1. 在block中,如果订阅之后,不想传值,可以使用协议中的方法
[subscriber sendNext:nil],若想传,则在该方法后传入参数(必须id类)
2. 传值完成之后,必须调用
sendCompleted方法,以示订阅完成,否则,无法再次订阅信号。
3. 如果返回值为ERROR,且要传递,调用
sendError方法,
sendNext方法无法传递错误信息
了解了信号和订阅,我们就可以开始简单的使用一下了。
假如界面中有一个textview一个label. 我们可以绑定他们两个,以达到用户在textview中输入的值,显示在label中
//界面的布局之类的不再写,只写下逻辑块代码 [self.textview.rac_textSignal subscribeNext:^(id x) { self.label.text = (NSString *)x; }];
代码中的rac_textSignal返回的是textView的文本信号。我们订阅之后,就可以获得这个信号,其中的
id x就是textview中的文本,所以我们直接赋值给
label.text就OK了
RACCommand
这个类,是我花了好久时间才看懂的一个类。主要从事事件的处理。比如网络请求这种,一方传递出去一个信号,需要另一方返回一个信号的这种流程。前后端通信这种就是一个很好的例子: 前端post数据给后端,然后服务器返回给我们相应的JSON字符串,这个就可以使用RACCommand来处理。
与RACSignal的区别, RACSignal是单向的,只能后者订阅前者,反之则不行
创建方法
- (id)initWithSignalBlock:(RACSignal * (^)(id input))signalBlock;
该方法只有一个 block参数,创建的时候,要在block中创建一个RACSignal,并返回,如果不想返回信号,则可以
return [RACSignal empty];来返回一个空的信号。
- (id)initWithEnabled:(RACSignal *)enabledSignal signalBlock:(RACSignal * (^)(id input))signalBlock;
该方法除了一个 block参数之外,还有一个enable信号,用来控制command能否执行。
执行方法
- (RACSignal *)execute:(id)input;
只有执行了,订阅的block才会调用
属性
RACCommand有几个属性executionSignals
executing
errors
这些属性的类型都是RACSignal. 目的就是在执行之后,可以订阅,查看信号内的内容。
allowsConcurrentExecution BOOL类型的变量,是否允许同时多个执行,默认是NO
注意:
一般情况下,[self.command.executionSignals subscribeNext:^(id x) {...}]订阅的时候,里边的x,是
RACSignal类的实例,我们此时订阅这个信号,就可以看到我们发出的信号(外层信号)的内容,如果想要看到里边包装的信号,也就是信号中的信号,我们可以使用
双重订阅
[self.command.executionSignals subscribeNext:^(RACSignal *x) { [x subscribeNext:^(id x){ NSLog(@ 4000 "%@",x); }]; }]
RAC的高级用法
// switchToLatest 获取信号中信号最近发出信号,订阅最近发出的信号。 [[self.command.executionSignals switchToLatest] subscribeNext:^(id x) { NSLog(@"%@",x); }];
switchToLatest方法: 获取信号中信号最近发出信号,订阅最近发出的信号。也就是可以跳过外层的信号,直接订阅内部的信号。
或者
//跳过第一次订阅,直接订阅第二次 [[self.command.executionSignals skip:1] subscribeNext:^(id x) { NSLog(@"%@",x); }];
RACCommand的用法
//1,先创建 RACCommand *cmd = [[RACCommand alloc]initWithSignalBlock:^RACSignal *(id input) { return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { [subscriber sendNext:@"传值"]; [subscriber sendCompleted]; return nil; }]; }]; //2. 订阅 //信号中的信号,要双层订阅 [cmd.executionSignals subscribeNext:^(id x) { NSLog(@"%@",x); //<RACDynamicSignal: 0x79e99050> name: [x subscribeNext:^(id inner) { NSLog(@"%@",inner); //传值 }]; }]; //开始订阅的时候会打Log [cmd.executing subscribeNext:^(id x) { NSLog(@"executing"); }]; //如果出错了,会走这里 [cmd.errors subscribeNext:^(id x) { NSLog(@"error"); NSLog(@"%@",x); }]; //3. 执行 [cmd execute:@"执行"];
RACSubject
介绍
RACSubject:信号提供者,自己可以充当信号,又能发送信号。使用步骤
创建信号 [RACSubject subject],跟RACSiganl不一样,创建信号时没有block。订阅信号 - (RACDisposable *)subscribeNext:(void (^)(id x))nextBlock
发送信号 sendNext:(id)value
一般用途
替换代理,block//在第二个VC里边声明一个RACSubject属性,以反向传值为例,第二个页面的背景色为随机色,点击按钮POP回第一个页面,然后把颜色传给第一个页面 @interface SecViewController : UIViewController @property (nonatomic,strong) RACSubject *delegateSubject; @end @implementation SecViewController - (void)viewDidLoad { [super viewDidLoad]; UIButton *btn = [[UIButton alloc]init]; [btn setTitle:@"点击传值" forState:UIControlStateNormal]; btn.frame = CGRectMake(0, 0, 100, 44); btn.center = self.view.center; [[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) { if (self.delegateSubject) { [self.delegateSubject sendNext:self.view.backgroundColor]; [self.delegateSubject sendCompleted]; } [self.navigationController popViewControllerAnimated:YES]; }]; [self.view addSubview:btn]; } //第一个页面的PUSH代码 - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { SecViewController *sec = [[SecViewController alloc]init]; sec.delegateSubject = [RACSubject subject]; [sec.delegateSubject subscribeNext:^(id x) { NSLog(@"%@",x); self.view.backgroundColor = (UIColor *)x; }]; sec.hidesBottomBarWhenPushed = YES; [self.navigationController pushViewController:sec animated:YES]; }
可以在代码段里看到一个
[btn rac_signalForControlEvents:UIControlEventTouchUpInside],这个方法会返回按钮的command中的信号,系统默认为我们做的,我们只要订阅这个信号,就可以执行按钮对应的Action了
RACTuple
RAC中的元组类,用来包装值,类似NSArray创建方法
+ (instancetype)tupleWithObjectsFromArray:(NSArray *)array; + (instancetype)tupleWithObjectsFromArray:(NSArray *)array convertNullsToNils:(BOOL)convert; + (instancetype)tupleWithObjects:(id)object, ... NS_REQUIRES_NIL_TERMINATION;
属性
@property (nonatomic, readonly) NSUInteger count; /// These properties all return the object at that index or nil if the number of /// objects is less than the index. @property (nonatomic, readonly) id first; @property (nonatomic, readonly) id second; @property (nonatomic, readonly) id third; @property (nonatomic, readonly) id fourth; @property (nonatomic, readonly) id fifth; @property (nonatomic, readonly) id last;
所以我们可以使用索引来获取Tuple中的值
NSArray *testArr = @[@"1",@"2",@"3",@"4"]; RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:testArr]; NSLog(@"%@",tuple[0]); //1 NSLog(@"%@",tuple[1]); //2 NSLog(@"%@",tuple.third); //3
如果遇到NSDictionary,则RACTuple则会循环键值对的个数次,倒叙的遍历字典的键值对(通常是把一个键值对当成数组的一个元素)
NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)}; [testDic.rac_sequence.signal subscribeNext:^(RACTuple *x) { NSLog(@"%@",x); NSLog(@"%ld",(long)x.count); NSLog(@"%@",x[0]); NSLog(@"%@",x[1]); NSLog(@"%@",x.third); }]; 循环 第一次,log结果:(<RACTuple: 0x79e49550> (age,20) 2 age 20 (null)) 第二次结果:(<RACTuple: 0x7c183bc0> (name,testData) 2 name testData (null))
我们可以使用RACTupleUnpack把tuple转换成dictionary的键值关系
RACTupleUnpack(NSString *key, NSString *value) = x;
上面的写法和下面的得到的结果是一样的
NSString *key = x[0]; NSString *value = x[1];
RACSequence
简介
RAC中快速遍历Array和Dictionary的类用途
遍历数组字典NSArray *testArr = @[@"1",@"2",@"3",@"4"];
[testArr.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x); //<RACTuple: 0x796386c0> ( number,1 )
}];
NSDictionary *testDic = @{@"name":@"testData",@"age":@(20)};
[testDic.rac_sequence.signal subscribeNext:^(id x) {
NSLog(@"%@",x);
#if 0
RACTupleUnpack(NSString *key, NSString *value) = x;
NSLog(@"%@,%@",key,value);
#else
NSString *key = x[0]; NSString *value = x[1];
NSLog(@"%@,%@",key,value);
#endif
}];
字典转模型
//OC写法 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil]; NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath]; NSMutableArray *items = [NSMutableArray array]; for (NSDictionary *dict in dictArr) { FlagItem *item = [FlagItem flagWithDict:dict]; [items addObject:item]; } //RAC写法 NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil]; NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath]; NSMutableArray *flags = [NSMutableArray array]; _flags = flags; // rac_sequence注意点:调用subscribeNext,并不会马上执行nextBlock,而是会等一会。 [dictArr.rac_sequence.signal subscribeNext:^(id x) { // 运用RAC遍历字典,x:字典 FlagItem *item = [FlagItem flagWithDict:x]; [flags addObject:item]; }]; NSLog(@"%@", NSStringFromCGRect([UIScreen mainScreen].bounds)); //RAC高级写法: NSString *filePath = [[NSBundle mainBundle] pathForResource:@"flags.plist" ofType:nil]; NSArray *dictArr = [NSArray arrayWithContentsOfFile:filePath]; // map:映射的意思,目的:把原始值value映射成一个新值 // array: 把集合转换成数组 // 底层实现:当信号被订阅,会遍历集合中的原始值,映射成新值,并且保存到新的数组里。 NSArray *flags = [[dictArr.rac_sequence map:^id(id value) { return [FlagItem flagWithDict:value]; }] array];
rac_signalForSelector
简介
可以代替代理#pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"Message" message:@"You will push to the second ViewController,right?" delegate:self cancelButtonTitle:@"cacel" otherButtonTitles:@"okay", nil]; #pragma clang diagnostic pop [alert show]; [[self rac_signalForSelector:@selector(alertView:clickedButtonAtIndex:) fromProtocol:@protocol(UIAlertViewDelegate)] subscribeNext:^(RACTuple *x) { NSLog(@"%@",x); if ([x[1] boolValue]) { [self pushToSecViewController]; } }]; //AlertView的简化写法 [[alertView rac_buttonClickedSignal] subscribeNext:^(id x) { NSLog(@"%@",x); }];
self.textField.delegate = self; [[self rac_signalForSelector:@selector(textFieldDidBeginEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(id x) { NSLog(@"开始编辑"); }]; [[self rac_signalForSelector:@selector(textFieldDidEndEditing:) fromProtocol:@protocol(UITextFieldDelegate)] subscribeNext:^(id x) { NSLog(@"结束编辑"); }];
跟RACSubject区别
RACSubject可以传值rac_signalForSelector: fromProtocol: 不可以传值
代替KVO
rac_valuesAndChangesForKeyPath// 把监听view的center属性改变转换成信号,只要值改变就会发送信号 // observer:可以传入nil [[view rac_valuesAndChangesForKeyPath:@"center" options:NSKeyValueObservingOptionNew observer:nil] subscribeNext:^(id x) { NSLog(@"%@",x); }];
代替通知
rac_addObserverForName// 把监听到的通知转换信号 [[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(id x) { NSLog(@"键盘弹出"); }];
处理当界面有多次请求时,需要都获取到数据才能进行下一步
rac_liftSelector:withSignalsFromArray:Signals当传入的Signals(信号数组),每一个signal都至少sendNext过一次,就会去触发第一个selector参数的方法
//处理多个请求,都返回结果的时候,统一做处理. RACSignal *signal1 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) { // 发送请求1 [subscriber sendNext:@"发送请求1"]; return nil; }]; RACSignal *signal2 = [RACSignal createSignal:^RACDisposable *(id<racsubscriber> subscriber) { // 发送请求2 [subscriber sendNext:@"发送请求2"]; return nil; }]; // Selector调用:当所有信号都发送数据的时候调用 // 数组存放信号 // Selector注意点:参数根据数组元素决定 // Selector方法参数类型,就是信号传递出来数据 [self rac_liftSelector:@selector(doingSthWithData1: data2:) withSignalsFromArray:@[signal1,signal2]];
RAC常见宏
RAC(target, keyPath)
用法用于给某个对象的某个属性绑定。
例子
// 只要文本框文字改变,就会修改label的文字 RAC(self.labelView,text) = _textField.rac_textSignal;
RACOberve(self, name)
用法监听某个对象的某个属性,返回的是信号。
例子
[RACObserve(self.view, center) subscribeNext:^(id x) { NSLog(@"%@",x); }];
先写到这里,回头会有补充
本文demo地址另外还有一个使用RAC+MVVM模拟的登录小demo
参考资料:
学习RAC小记-适合给新手看的RAC用法总结
ReactiveCocoa(FRP)-进阶篇
被误解的 MVC 和被神化的 MVVM
ReactiveCocoa干货
相关文章推荐
- 插件管理框架 for Delphi(一)
- WPF MVVM示例讲解
- 使用CSS框架布局的缺点和优点小结
- 一起动手编写Android图片加载框架
- Android中Volley框架下保持会话方法
- 基于.NET平台常用的框架和开源程序整理
- Android的搜索框架实例详解
- 列举PHP的Yii 2框架的开发优势
- Windows窗体的.Net框架绘图技术实现方法
- 浅谈JavaScript 框架分类
- 微信小程序 框架详解及实例应用
- 轻量级javascript 框架Backbone使用指南
- 浅谈JavaScript前端开发的MVC结构与MVVM结构
- javascript实现框架高度随内容改变的方法
- Vue.js 和 MVVM 的注意事项
- 又一款MVVM组件 Vue基础语法和常用指令(1)
- JS刷新框架外页面七种实现代码
- 超赞的动手创建JavaScript框架的详细教程
- 深入探讨前端框架react
- Bootstrap框架的学习教程详解(二)