iOS开发之RAC+MVVM实战
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发之RAC+MVVM实战相关的知识,希望对你有一定的参考价值。
参考技术A 本文介绍两个开发中常用的场景,第一个是UITableView列表界面通过网络请求数据展示数据,第二个是登录功能。功能比较基础,但都是精髓。分享一下笔者对MVVM的一些见解,在此抛砖引玉,希望能对广大开发者提供一点思路。效果如上图,实现此功能用到的类:
1、OrderController
OrderController主要讲的是ViewModelEvent中的方法,其他也没什么可说的
2、RequestViewModel:主要向控制器提供数据,通知tableView刷新界面
RequestViewModel.h
RequestViewModel.m
3、OrderCell和OrderModel
跟之前MVC做法完全一致,其实没什么好说的
OrderCell.h
OrderCell.m
OrderModel.h
效果如上图,实现此功能用到的类:
1、LoginController
2、LoginViewModel
浅谈ReactiveCocoa之MVVM
简介
ReactiveCocoa(简称:RAC)为一个开源函数响应式编程框架;
使用场景:通过RAC可以更加方便编程进行MVVM设计模式编程;
核心机制为信号(信号流)。
由于Swift和OC版本存在的差异性比较大,维护团队直接给拆了一下: Swift版本(ReactiveSwift)和 OC版本(ReactiveCocoa)
写该篇文章的初衷: 如何使用RAC 和 如何借助RAC来逐步实现MVC到MVVM的迁移。
一、ReactiveCocoa初见
1、编程思想
ReactiveCocoa是函数式编程(Functional Programming)和响应式编程(Reactive Programming)集大成者;
2、实现关键
2.1. 每个方法必须有返回值(本身对象);
2.2. 把函数或者Block当做参数, block参数(需要操作的值)block返回值(操作结果);
3、ReactiveCocoa初见
3.1、 RACSiganl
最基本的信号类,默认为冷信号,表示当数据改变时,信号内部会发出数据,只有订阅了(subscribeNext)才会进被触发(编程热信号),代码演示如下:
// 1.创建信号
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// 3、发送信号信号
NSLog(@"信号被订阅 发送信号");
// 4、执行了这一步 订阅信号才会发触发 [subscriber sendNext:@"heheh"];
return nil; }];
// 2.订阅信号 ---必须为订阅
[signal subscribeNext:^(id _Nullable x) { // 发送信号的内容 NSLog(@"====%@", x);
}];
3.2、RACSubscriber
订阅者,用于发送信号,这是一个协议,不是一个类,只要遵守这个协议,并且实现方法才能成为订阅者。(示例也如上所示)
3.3、RACDisposable
用于取消订阅或者清理资源,当信号发送完成或者发送错误的时候,就会自动触发它。
使用场景:不想监听某个信号时,可以通过它主动取消订阅信号。代码延时如下:
// 1.创建
RACSignal *signal = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
// 3、发送信号信号
NSLog(@"信号被订阅 发送信号");
[subscriber sendNext:@"heheh"];
return [RACDisposable disposableWithBlock:^{
// 只要信号取消就会来这里
// 默认一个信号发送数据完毕就会主动取消订阅
NSLog(@"信号被取消了");
}];
}];
// 2.订阅信号 ---必须为订阅
RACDisposable *disposable = [signal subscribeNext:^(id _Nullable x) {
// 发送信号的内容
NSLog(@"====%@", x);
}];
// 3.取消订阅
[disposable dispose];
3.4、RACSubject
4.1. RACSubject:信号提供者,自己可以充当信号,又能发送信号。
4.2. 用场景:通常用来代替代理,有了它,就不必要定义代理了。
3.5、RACTuple
元组类,类似NSArray,用来包装值.代码演示如下:
RACTuple *tuple = [RACTuple tupleWithObjectsFromArray:@[@"hello111", @"hello222",@"hello333"]];
NSLog(@"%@", [tuple objectAtIndex:0]);
3.6、RACSequence
RAC中的集合类,可代NSArray,NSDictionary,可以使用它来快速遍历数组和字典。
1、RACSequence代替数组
- (void)testRACSequenceArr{
NSArray * arr = @[@"123", @"asdfas", @1];
// 订阅集合信号,内部会自动便利所有的元素发出来
[arr.rac_sequence.signal subscribeNext:^(id _Nullable x) {NSLog(@"%@", x);
}];
}
2、RACSequence代替字典
- (void)testRACSequenceDict{
NSDictionary *dict = @{ @"name" : @"张三", @"age" : @22};
[dict.rac_sequence.signal subscribeNext:^(RACTuple * _Nullable x) {
// 方法一、
// NSLog(@"%@ %@", x[0], x[1]);
// 方法二、
// 用来解析元组, 宏里面的参数,传需要解析出来的变量名
// = 右边,放需要解析的元组RACTupleUnpack(NSString *key, NSString *value) = x;NSLog(@"%@ %@", key, value );
}];
}
3.7、RACCommand
3.7.1、直译为命令,只是一个继承自 NSObject 的类,但是它却可以用来创建和订阅用于响应某些事件的信号。
3.7.2、相对而言比较复杂
3.7.3、使用场景:网络请求(MVVM设计模式中网络模块)
3.7.4、在默认情况下 RACCommand 都是不支持并发操作的,需要在上一次命令执行之后才可以发送下一次操作,如果直接execute:两次,最终也只会执行第一个execute:; 所以谨记 在使用应用中推荐一个网络请求对应一个command;
代码演示如下:
简单使用 - 用于网络请求
- (void)RACCommandSimpleUse{
RACCommand *command = [[RACCommand alloc] initWithSignalBlock:^RACSignal * _Nonnull(NSNumber * _Nullable input) {
return [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
[subscriber sendNext:input];
// 每次sendNext 记得sendCompleted
[subscriber sendCompleted];
return nil;
}];
}];
[[command.executionSignals switchToLatest] subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
// 在默认情况下 RACCommand 都是不支持并发操作的,需要在上一次命令执行之后才可以发送下一次操作,如果直接execute:两次,最终也只会执行第一个execute:
// 所以谨记: 一个command对应一个网络请求
[command execute:@"网络请求1"];
// [command execute:@"网络请求2"];
[RACScheduler.mainThreadScheduler afterDelay:0.5schedule:^{
[command execute:@"网络请求2"];
}];
}
3.8、RACMulticastConnection
3.8.1、直译为多播连接;
3.8.2、存在的问题普通的信号在执行sendNext:的时候,都会重新再执行以下信号的创建,当你想在一个请求完成后 进行分多级刷新UI 或者 做一些别的操作,如果直接用普通的信号进行sendNext:时候,则会进行多次网络请求操作;
3.8.3、项目中使用到的场景比较少;
代码演示:
-(void)RACMulticastConnectionUse{
// 1、通过信号创建链接
RACMulticastConnection *connection = [[RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"完毕");
[subscriber sendNext:@"Send Request"];
// 每次sendNext 记得sendCompleted
// [subscriber sendCompleted];
return nil;
}] publish];
// 订阅信号(通过链接转换的信号)一次
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"一次 x : %@", x);}];
// 订阅信号(通过链接转换的信号)二次
[connection.signal subscribeNext:^(id _Nullable x) {
NSLog(@"二次 x : %@", x);}];
[connection connect];
// 且只有第一次连接才会有效果
// [connection.signal subscribeNext:^(id _Nullable x) {
// NSLog(@"重新连接第一次 x : %@", x);
// }];
// [connection.signal subscribeNext:^(id _Nullable x) {
// NSLog(@"重新连接第二次 x : %@", x);
// }];
// [connection connect];}
二、ReactiveCocoa使用场景
1、代替代理
1.1、对象持有signal, 推荐用这种,代码如下:
- (void)useRACInstandDelegate{
[self.redView.btnClickSignal subscribeNext:^(id _Nullable x) {
NSLog(@"%@", x);
}];
}
1.2、使用rac_signalForSelector来进行方方法的执行,类似于系统自带方法performSelector:withObject:,不推荐(硬编码 和 警告),代码如下:
- (void)useRACInstandDelegate2{
[[self.redView rac_signalForSelector:@selector(btnClick:)] subscribeNext:^(id _Nullable x) {
NSLog(@"红色view上面的按钮点击了");
}];
}
2、代替KVO,代码演示如下:
- (void)insteadKVO{
// 需手动导入:#import "NSObject+RACKVOWrapper.h"
// 1.代替KVO 方法一
[self.redView rac_observeKeyPath:@"frame"options:NSKeyValueObservingOptionNewobserver:nil block:^(id value,NSDictionary *change,BOOL causedByDealloc,BOOL affectedOnlyLastComponent) {
//
}];
// 2.替代KVO 方法二
[[self.redView rac_valuesForKeyPath:@"frame" observer:nil] subscribeNext:^(id _Nullable x) {
// 打印的是NSRect
NSLog(@"%@", x);
}];
3、监听按钮的点击事件,代码演示如下:
- (void)monitorBtnClick{
UIButton *btn;
[[btn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(__kindof UIControl * _Nullable x) {
// 按钮点击处理事件
}];
}
4、代替通知,代码演示如下:
- (void)insteadNotification{
[[[NSNotificationCenter defaultCenter] rac_addObserverForName:UIKeyboardWillShowNotification object:nil] subscribeNext:^(NSNotification * _Nullable x) {
// 处理通知事件
}];
}
5、监听文本框文案,代码演示如下
- (void)insteadTextInput{
UITextField *textField;
[textField.rac_textSignal subscribeNext:^(NSString * _Nullable x) {
// 监听到文本的改变
}];
}
6、处理当界面有多次请求时,需要都获取到数据时,才能展示界面,代码演示如下
- (void)multiRequestData{
RACSignal *signal1 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"发送木块一的数据");
[subscriber sendNext:@"发送木块一的数据"];
return nil;
}];
RACSignal *signal2 = [RACSignal createSignal:^RACDisposable * _Nullable(id<RACSubscriber> _Nonnull subscriber) {
NSLog(@"发送模块二的数据");
[subscriber sendNext:@"发送模块二的数据"];return nil;}];
// 数组:存放信号
// 当数组中的所有信号都发送完成的时候,才会执行Selector
// 方法的参数: 必须跟数组一一对应
// 方法的参数:就是每一个信号发送的数据[self rac_liftSelector:@selector(updateUIFirstPartData:secondPartData:)withSignalsFromArray:@[signal1, signal2]];
}
- (void)updateUIFirstPartData:(NSString *)firstPartData secondPartData:(NSString *)secondPartData{
NSLog(@"更新 UI%@ %@",firstPartData , secondPartData);
}
三、浅谈MVVM
简介:
MVVM,个人理解他就是MVC的升级版,解耦版,它是一种双向绑定(data-binding):View的变动,自动反映在 ViewModel,反之亦然;MVVM设计模式并不一定要借助RAC来实现,但若使用RAC来实现会更加的简单(因为所有的操作和响应都通过信号来完成对接);
M : 最基本的模型数据
V : 视图 / 控制器
VM : 处理业务的逻辑(eg:操作事件、数据请求等)
1.项目目录结构的体现(给你一种既视感 以上是关于iOS开发之RAC+MVVM实战的主要内容,如果未能解决你的问题,请参考以下文章