内容简介:通过上篇文章,我们了解到利用mvp可以对mvc的c层瘦身,并使得层次分明,且网络请求变得通用。那就已经很完美了,那么mvvm是用来干嘛的呢。对于mvvm它是用来描述数据和视图的关系的。我们开发过程中常常发现数据变化会导致视图的变化,如一个列表,当没有数据的时候要展示空界面,当有数据的时候要展示对应条数的列表界面。在比如当网络请求回来的列表中的一个为已点赞数据,那么界面的相应视图就要显示红心其他都为灰心。以上示例都反应了数据和视图的这种绑定关系,并且数据一变化视图就要相应的进行变化,也就是所谓的数据驱动。
通过上篇文章,我们了解到利用mvp可以对mvc的c层瘦身,并使得层次分明,且网络请求变得通用。那就已经很完美了,那么mvvm是用来干嘛的呢。
对于mvvm它是用来描述数据和视图的关系的。我们开发过程中常常发现数据变化会导致视图的变化,如一个列表,当没有数据的时候要展示空界面,当有数据的时候要展示对应条数的列表界面。在比如当网络请求回来的列表中的一个为已点赞数据,那么界面的相应视图就要显示红心其他都为灰心。
以上示例都反应了数据和视图的这种绑定关系,并且数据一变化视图就要相应的进行变化,也就是所谓的数据驱动。
以上介绍在说明数据驱动视图这件事,这和mvc,mvp是不同的,导致了编码思想也不同。
数据一变化视图就要变化,也就是要监听数据的变化,这可以利用kvo。但是苹果中的kvo用起来很麻烦,首先要注册kvo,并在界面销毁时候要释放kvo。而RAC的出现解决了这个麻烦,从而使得mvvm的写法变得更方便了。也就是说mvvm要用到kvo思想来监听数据源的变化,从而让视图变化。rac代替了kvo的这种繁琐写法。mvvm用的是kvo思想,rac不是必须的,但是用了更方便而已。关于rac的用发可以参考这里
MVVM
M:Model,它是模型类,就是有很多属性,每个属性名字和后台返回的字段名相同。
V:View,它是视图类,如继承与UIView类的所有类都可以认为是V层,并且Controller也属于V层,即继承与UIView,UIViewController类的所有类都属于V层。这个类里存放的都是和视图相关的逻辑代码。并且controller类里存放的是一些胶水代码(就是起到粘合作用的代码)。和p层有区别,加入了视图和数据源的绑定机制。
VM:viewModel,它是vm层,这里是主要写网络请求,数据逻辑的地方。处理和视图无关的数据逻辑等。这个类不该持有v层,也就是不该#import <UIKit/UIKit.h>。并且该类和p层是有区别的,它不是通过回调的方式返回到v层。而是通过kvo的方式。
三者关系是,v层持有vm层,vm层持有m层,并通过kvo的方式通知v层。
通过以上分析,其实mvvm设计模式和mvp很像。只不过是把p层中的网络请求的回调去掉了,通过kvo的方式返回到v层。并把视图和vm层进行绑定,将p层进行这两个修改后重新起一个名字叫做vm层,也就变成了mvvm了。
下边我们用一些伪代码来进一步探索MVVM的应用,其实和MVP一样,我们也需要封装出一套MVVM的基类。用这些基类可以使得我们编码更方便。
//ArticleListModel.h文件 #import <Foundation/Foundation.h> NS_ASSUME_NONNULL_BEGIN @interface ArticleListModel : NSObject /** 文章内容 */ @property (copy, nonatomic) NSString *content; /** 点赞否 */ @property (assign, nonatomic) NSInteger praise; @end NS_ASSUME_NONNULL_END //ArticleListModel.m文件 #import "ArticleListModel.h" @implementation ArticleListModel @end //ArticleListVC.h文件 #import "BSMvpUtilsBaseViewController.h" NS_ASSUME_NONNULL_BEGIN @interface ArticleListVC : BSMvpUtilsBaseViewController @end NS_ASSUME_NONNULL_END //ArticleListVC.m文件 #import "ArticleListVC.h" #import "ArticleListVM.h" @interface ArticleListVC () @property (strong, nonatomic) ArticleListVM *vm; @property (strong, nonatomic) UITableView *tableView; @end @implementation ArticleListVC - (void)viewDidLoad { [super viewDidLoad]; //v层持有vm层 self.vm = [[ArticleListVM alloc] init]; //初始化视图 [self setUpUI]; //视图与数据源绑定 [self bindView]; //网络请求 [self.vm fetchData]; } - (void)setUpUI{ //tableView的布局等视图初始化代码 } - (void)bindView{ @weakify(self); [[RACObserve(self.vm, error) skip:1] subscribeNext:^(id _Nullable x) { @strongify(self); if (self.vm.error) { //如果有错,弹出错误Toast等 } }]; [[RACObserve(self.vm, arrList) skip:1] subscribeNext:^(id _Nullable x) { @strongify(self); if (self.vm.arrList.count == 0) { //显示无数据占位图等 }else{ //隐藏无数据占位图等 } //tableView刷新数据源 [self.tableView reloadData]; }]; } //列表中的某一个点赞的心形被点击 - (void)cellPraiseBtnClick:(NSIndexPath *)indexPath{ //根据indexPath找到对应的数据源,然后根据数据源的状态请求相应的接口,最后改变数据源的状态。 [self.vm praiseMethod:indexPath.row]; } @end //ArticleListVM.h文件 #import "BSMvvmUtilsBaseVM.h" @class ArticleListModel; NS_ASSUME_NONNULL_BEGIN @interface ArticleListVM : BSMvvmUtilsBaseVM /** 出错 */ @property (strong, nonatomic) NSError *error; /** 点赞列表数据源 */ @property (copy, nonatomic) NSArray<ArticleListModel *> *arrList; - (void)fetchData; /** 点赞和取消点赞方法 @param index index */ - (void)praiseMethod:(NSInteger)index; /** 点赞,取消点赞请求 @param isPraise 1:点在 0:取消点赞 */ - (void)fetchPraise:(NSNumber *)isPraise; @end NS_ASSUME_NONNULL_END //ArticleListVM.m文件 #import "ArticleListVM.h" #import "ArticleListModel.h" #import <BSUtilsBaseRequestManager.h> @implementation ArticleListVM - (instancetype)init{ self = [super init]; if (self) { _error = nil; _arrList = nil; } return self; } //网络请求 - (void)fetchData{ [[BSUtilsBaseRequestManager new] postWithUrl:@"xxx" parmas:@{} finishBlk:^(NSDictionary * _Nonnull responseObject, NSError * _Nonnull error) { if (error) { self.error = error; }else{ //数据转模型,并把转换的结果数据赋值给arrList self.arrList = @[]; } }]; } //点赞和取消点赞方法 - (void)praiseMethod:(NSInteger)index{ NSMutableArray *arrTemp = [NSMutableArray arrayWithArray:self.arrList]; ArticleListModel *model = self.arrList[index]; NSInteger isPraise = model.praise; if (isPraise) { //当前是已经点过攒了 //下边要请求取消点赞接口,并判断是否请求成功 if(!self.error){ //取消点赞成功 model.praise = 0; [arrTemp replaceObjectAtIndex:index withObject:model]; self.arrList = arrTemp; } }else{ //当前是还没有点过攒 //下边要请求点赞接口,并判断是否请求成功 if(!self.error){ //点赞成功 model.praise = 1; [arrTemp replaceObjectAtIndex:index withObject:model]; self.arrList = arrTemp; } } } //点赞,取消点赞请求 - (void)fetchPraise:(NSNumber *)isPraise{ if(isPraise){ [[BSUtilsBaseRequestManager new] postWithUrl:@"xxx" parmas:@{@"praise":isPraise} finishBlk:^(NSDictionary * _Nonnull responseObject, NSError * _Nonnull error) { self.error = error; }]; } } @end 复制代码
总结
通过以上分析我们知道了mvvm是要把vm层和v层进行绑定的,然后我们只要改变vm层的数据源即可改变视图。这是关键。首先进行数据源和视图的绑定再改变数据源。
其实所谓的vm层和v层绑定其实就是监听vm层的数据源变化从而改变视图罢了。这种绑定利用RAC会更加方便。当然了不用RAC也可以的只是麻烦些而已。
总是要记住首先vm层和视图层的绑定,之后只要操作vm层的数据源即可。比如上述的代码我要显示列表界面,首先监听vm层的arrList,只要有变化就进入回调,在回调中判断是否要显示空界面还是具体的列表界面。这就是所谓的绑定。之后我们网络请求,如果请求成功就对arrList赋值,这个过程就是在改变vm层的数据源,从而使得视图更新。
在比如我们要对列表中的某个心形进行点击(这时候心形有两种状态的),进行点赞或者取消点赞。这时候我们根据index找到对应的数据源,进行网络请求,在把对应的数据源praise状态改变重新赋值给vm层的arrList属性即可,因为之前arrList数据源在和视图绑定着呢。所以这时候改变arrList从而更新了视图。
当你用了mvvm这种编码方式时候,可以感觉到减少了刷新代码,不必每次改变完数据后都要手动去写刷新视图的代码了。原因是绑定只要绑定一次即可,就在不停的监听数据的变化了。其实不仅仅是减少了刷新视图的代码,同时编码方式变了,思想变了。变成了数据驱动视图了。至于mvp好还是mvvm好,我个人觉得mvvm这种思想还是挺有趣的,同时这种数据驱动视图确实是普遍现象。而且这是一种新的有趣的编码方式。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
C++ Primer 中文版(第 5 版)
[美] Stanley B. Lippman、[美] Josée Lajoie、[美] Barbara E. Moo / 王刚、杨巨峰 / 电子工业出版社 / 2013-9-1 / CNY 128.00
这本久负盛名的 C++经典教程,时隔八年之久,终迎来史无前例的重大升级。除令全球无数程序员从中受益,甚至为之迷醉的——C++ 大师 Stanley B. Lippman 的丰富实践经验,C++标准委员会原负责人 Josée Lajoie 对C++标准的深入理解,以及C++ 先驱 Barbara E. Moo 在 C++教学方面的真知灼见外,更是基于全新的 C++11标准进行了全面而彻底的内容更新。......一起来看看 《C++ Primer 中文版(第 5 版)》 这本书的介绍吧!