通俗易懂图解MVVM和RAC双向绑定介绍(附Demo)
来源:互联网 发布:mac全屏后切换桌面 编辑:程序博客网 时间:2024/05/16 13:58
前言
其实MVVM就是MVC的进化版本,相对于臃肿的Controller,代码越来越多之后,有一部分人就用了新的设计模式,其实看久了也没什么,通俗点讲,其实就是把之前Controller里面的代码逻辑全部移植到了ViewModel里面,相对于以前而言,控制器也被归属于View一类,那么他和View一样都会有自己的ViewModel去处理逻辑,而且ViewModel必然拥有Model,这样的关系使得控制器代码会减少很多很多,处理起来又多了一个类,本身设计模式里面有代理,通知,KVO等,不同业务对应不同的设计模式,个人理解为了减少控制器的代码,引进了新的类,那么类的交互就变得更麻烦了,因此RAC出现了,他帮我们直接管理了苹果的那一套数据处理设计模式,统一用它的”信号流”来进行,谁用谁知道啊。。。。。。
简单看下自己理解的MVVM
效果图
图片和文字闪烁的效果传送门
闪烁效果
RAC的第一种流程介绍—>RACSignal
网上很多基本的介绍,这里主要讲一下流程
1.如果用RACSignal
来创建信号(内部Block有发送信号以及取消信号的回调,为什么是3和4呢,原因在后面)
// 1.创建信号RACSignal *signal = [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {// 3.发送信号[subscriber sendNext:@"mkj"];[subscriber sendCompleted];// 4.取消信号,如果信号想要被取消,就必须返回一个RACDisposable// 当信号被手动或者自动取消订阅之后会回调到这里,进行一些资源的释放return [RACDisposable disposableWithBlock:^{NSLog(@"取消订阅");}];}];
注意:上面创建的方法内部代码主要归结于创建一个集成于RACSignal
的子类—>RACDynamicSignal
,然后
通过静态方法实例化出来,并把传进去的任务Block进行对象属性的存储
+ (RACSignal *)createSignal:(RACDisposable * (^)(id<RACSubscriber> subscriber))didSubscribe { RACDynamicSignal *signal = [[self alloc] init]; signal->_didSubscribe = [didSubscribe copy]; return [signal setNameWithFormat:@"+createSignal:"];}
2.创建的信号RACSignal(子类RACDynamicSignal)来调用第二步[Signale subscribeNext:^{}];
来进行信号的订阅,内部转换代码
RACSubscriber *o = [RACSubscriber subscriberWithNext:nextBlock error:NULL completed:NULL]; return [self subscribe:o];
注意:这里RAC的设计者向我们隐藏了RACSubscriber
,对外暴露了RACSignal
,所有的内部工作都由RACSubsriber
进行完成传递
如果你再点进去,这里传递的Signal,会判断之前创建的时候传进的Block是否为空,如果有任务,直接回调Block
RACDisposable *innerDisposable = self.didSubscribe(subscriber);
3.现在有人订阅了,又回调了Block,然后触发任务,完成之后调用
[subscriber sendNext:@"mkj"];[subscriber sendCompleted];// 内部代码最终调用方法如下- (void)sendNext:(id)value { @synchronized (self) { void (^nextBlock)(id) = [self.next copy]; if (nextBlock == nil) return; nextBlock(value); }}最终回调到订阅的时候NextBlock的任务
4.这个可有可无,返回一个RACDisposable,对订阅取消进行资源的释放
总结:把他比喻成工厂,当你需要打开生产流水线的时候(创建信号,带有任务),这个时候你工人都没有,根本不会走你的任务
信号不会被传递,而你有工人来的时候(就是订阅了信号),这个时候流水线才开始进行加工,这就是个人理解的冷信号模式
也可以把冷信号理解为未被订阅的信号,理解为信号里面的任务是不会进行的,只有订阅者的加入,信号才会变成热信号
也就是这玩意需要手动开启信号
RAC第二种的流程介绍—>RACSubject(继承与RACSignal)
1.创建信号 RACSubject *subject = [RACSubject subject];
该方法和上面的创建方式有所不同,他的实例化出来之后只是创建了一个数组,专门用来存储订阅信号的订阅者
2.订阅信号
[subject subscribeNext:^(id x) {// 当信号sendNext的时候会回调NSLog(@"%@",x);}];// 这方法也是和上面的有所区别,RACSubject该对象会把订阅者放到之前创建的数组里面,然后啥都不做了
3.[subject sendNext:value];
内部代码[self enumerateSubscribersUsingBlock:^(id<RACSubscriber> subscriber) { [subscriber sendNext:value]; }];可以看出,当他调用sendNext的时候,是会进行数组的遍历,然后挨个对订阅者发送消息
总结:其实这就是所谓的热信号模式,还是拿工厂来做比喻,RACSubject
是不管你有没有人订阅,我工厂24小时开启流水线
我管你有没有人加工,有人来了,我就用数组登记一下,信号来了的时候你们就负责接收任务,没人的时候我还是就好比我的员工
花名册是空的,但是照样生产,只是没人做事罢了,那么这里的RAC信号流就是没人处理罢了,会被丢弃
知识点:区别RACSubject和RACSignal
1.我个人理解,前者是冷信号模式,需要有人订阅才开启热信号,后者是热信号默认,不管你有没有订阅
2.前者其实是一旦有人订阅,就会去做对应的一组信号任务,然后进行回调,可以理解为有人的时候任务启动,没人的时候挂机
没错,我是把它简单理解为代理,后者是热信号,信号负责收集订阅者数组,发信号的时候回遍历订阅者,一个个执行任务
你可以把它理解为通知,我管你有没有接收,我照样发送,没人就丢弃
3.前者个人用来进行网络请求,后者进行类似代理或者通知的数据传递模式,这样就可以简单的理解为,RAC其实就是把apple的一套
delegate,Notification,KVO等一系列方法综合起来了,用起来更舒服罢了
4.那么MVVM模式下,本身就多了个ViewModel,交互起来需要更多的设计模式协助,RAC就解决了这个问题,直接用这个设计模式来搞
数据传递和监听的代码就清晰很多了
既然已经了解了RAC的流程,Demo走起!!!
MVVM + RAC示例Demo
1.用到了网络请求的信号传递
2.用RACObserve宏进行属性的KVO观察
3.用RACSubject进行数据的回调
4.用RACSequence进行异步数组和字典(打印的是RACTuple)的遍历
5.RAC–>combineLatest的方法进行简单多输入框登录注册页面模拟
先看下Demo里面各个类的关系
1.首先看下用MVVM的基类(MKJBaseViewController)
// baseVC的基础ViewModel// 子类重写就能覆盖类型@property (nonatomic,strong,readonly) MKJBaseViewModel *viewModel;/** 唯一初始化方法 @param viewModel 传入ViewModel @return 实例化控制器对象 */- (instancetype)initWithViewModel:(MKJBaseViewModel *)viewModel;/** 布局UI 子类重写 */- (void)setupLayout;/** 请求网络数据 绑定数据 子类重写 */- (void)setupBinding;/** 设置数据回调,点击事件处理 子类重写 */- (void)setupData;初始化的时候MKJDemoViewModel *viewModel = [[MKJDemoViewModel alloc] init]; MKJDemoViewController *demoVC = [[MKJDemoViewController alloc] initWithViewModel:viewModel]; [self.navigationController pushViewController:demoVC animated:YES];
2.再看一下TableViewController的基类
基础的实现和普通拥有tableView的控制器一样,无非区别在于代理的逻辑和数据交给了ViewModel
// 交给子类实现,传递最终的cell类- (Class)cellClassForRowAtIndexPath:(NSIndexPath *)indexPath{ @throw [NSException exceptionWithName:@"抽象方法未实现" reason:[NSString stringWithFormat:@"%@ 必须实现抽象方法 %@",[self class],NSStringFromSelector(_cmd)] userInfo:nil];}#pragma mark - tableView datasource// 交给ViewModel去实现- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{ return [self.viewModel numberOfSections];}- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{ return [self.viewModel numberOfRowInSection:section];}- (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section{ return [self.viewModel heightForHeaderInSection:section];}- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section{ return [self.viewModel viewForHeaderInSection:section];}- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{ MKJBaseTableViewCell *cell = [[self cellClassForRowAtIndexPath:indexPath] cellForTableView:tableView viewModel:[self.viewModel cellViewModelForRowAtIndexPath:indexPath]]; cell.selectionStyle = [self.viewModel tableViewCellSelectionStyle]; return cell;}- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ CGFloat height = tableView.rowHeight; NSNumber *calculateHeight = [[self cellClassForRowAtIndexPath:indexPath] calculateRowHeightWithViewModel:[self.viewModel cellViewModelForRowAtIndexPath:indexPath]]; if (calculateHeight) { height = calculateHeight.floatValue; } return height;}
3.最终上层的ViewController核心代码,最终剩下就这么点代码了,瘦身不。。。。
// 基本布局代码,顺便设置个RACObserve- (void)setupLayout{ [super setupLayout]; @weakify(self); [RACObserve(self.viewModel, isNeedRefresh) subscribeNext:^(id x) { @strongify(self); if ([x boolValue]) { [self.tableView reloadData]; } }];}// ViewModel进行网络请求- (void)setupBinding{ [super setupBinding]; @weakify(self) [self.viewModel sendRequest:^(id entity) { @strongify(self); [self hideLoadingViewFooter]; [self.tableView reloadData]; } failure:^(NSUInteger errCode, NSString *errorMsg) { }];}// 返回对应的CellClass- (Class)cellClassForRowAtIndexPath:(NSIndexPath *)indexPath{ return [MKJDemoTableViewCell class];}
4.来看下ViewModel在做什么,核心还是网络请求,RAC信号流的网络请求
// 网络请求外部- (void)sendRequest:(MKJRequestSucceed)succeedBlock failure:(MKJRequestFailure)failBlock{ [[self.model requestDemoDatasWithPage:[self.currentPage integerValue] maxTime:self.currentMaxTime] subscribeNext:^(id data) { if (data) { self.entity = data; [self handlePagingEntities:self.entity.list totalCount:@(self.entity.info.count) cellViewModelClass:[MKJDemoTableViewCellViewModel class] maxTime:self.entity.info.maxtime]; } !succeedBlock ? : succeedBlock(data); }];}// 网络请求内部- (RACSignal *)getRequestWithURLString:(NSString *)URLString parametersDictionary:(NSDictionary *)paraterDictionary parserEntityClass:(Class)parseEntityClass{ // 根据异步请求创建一个新的RACSinal @weakify(self) return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) { @strongify(self); [self.httpHelper getRequestWithUrlString:URLString parametersDictonary:paraterDictionary entityClass:parseEntityClass completeBlock:^(id data) { [subscriber sendNext:data]; [subscriber sendCompleted]; }]; return nil; }];}根据之前上面介绍的RAC逻辑,外部注册订阅者的时候成为热信号,然后调用创建信号的Block,完成网络请求之后sendNext进行回调,设计如此,然后在ViewModel中把Model组装完毕,进行外部的TableView reload。然后再次调用代理方法的时候,会再次进到ViewModel里面获取已经组装好的数据返回给TableView的DataSource,OK了
5.Cell也单独配置了对应CellViewModel,就是在RAC网络请求回来之后,把实体Model,用CellViewModel来进行组装,只是把之前装数据Model的数组,用来装拥有数据模型的CellViewModel,明白一点,ViewModel拥有Model,就能搞定数据的逻辑处理
我TM的终于明白了,NO BB Show me the code了,这根本说不清楚,需要的同学还是直接看Demo吧,最终的逻辑转换就是上面的MVVM效果图,理解了就可以了,无非就是一种设计思想,不过加上RAC确实还不错。。。
我个人也比较喜欢看Demo,需要的还是直接开撸吧
正确Demo地址
- 通俗易懂图解MVVM和RAC双向绑定介绍(附Demo)
- MVVM和RAC介绍
- C#使用Xamarin开发可移植移动应用(4.进阶篇MVVM双向绑定和命令绑定)附源码
- WPF-MVVM双向绑定
- vue双向数据绑定原理探究(附demo)
- Android databinding 双向绑定(Demo)
- Vue MVVM双向数据绑定(个人理解)
- iOS架构模式MVC、MVP、MVVM(内附demo)
- RAC 双向绑定实现案例
- 剖析Vue原理&实现双向绑定MVVM
- 剖析Vue原理&实现双向绑定MVVM
- 剖析Vue原理&实现双向绑定MVVM
- 剖析Vue原理&实现双向绑定MVVM
- 手写VUE mvvm双向数据绑定
- Android MVVM 初探之 DataBinding 双向绑定
- js html双向绑定Demo
- 数据双向绑定练习demo
- iOS MVVM架构的介绍(内含Demo)
- 2-SAT入门(tarjan强连通分量)hihocoder 1467
- spring boot 推荐工程目录结构
- js笔记六:数组功能篇
- JVM垃圾回收(GC)原理
- Java MultiSet
- 通俗易懂图解MVVM和RAC双向绑定介绍(附Demo)
- jquery的treeview树形菜单使用教程
- C#笔记整理(一)
- SwitchCompat在menu中使用时出现的问题
- Python Socket 模拟SSH
- Jquery中uploadify上传文件
- xss之cookie窃取
- maven如何将本地jar安装到本地仓库
- js定时更换图片