什么是MVVM
MVVM就是在MVC的基础上分离出业务处理的逻辑到viewModel层,即:
model层,API请求的原始数据
view层,视图展示,由viewController来控制
viewModel层,负责业务处理和数据转化,简单来说,就是API请求完数据,解析成model,之后在viewModel中转化成能够直接被视图层使用的数据,交付给展示页面。
Model层是少不了的了,我们得有东西充当DTO(数据传输对象),当然,用字典也是可以的,编程么,要灵活一些。Model层是比较薄的一层,如果学过Java的小伙伴的话,对JavaBean应该不陌生吧。
ViewModel层,就是View和Model层的粘合剂,他是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他各种各样的代码的极好的地方。说白了,就是把原来ViewController层的业务逻辑和页面逻辑等剥离出来放到ViewModel层。
View层,就是ViewController层,他的任务就是从ViewModel层获取数据,然后显示。
view-model 一词的确不能充分表达我们的意图. 一个更好的术语可能是 “View Coordinator”(感谢Dave Lee提的这个 “View Coordinator” 术语, 真是个好点子)。你可以认为它就像是电视新闻主播背后的研究人员和作家团队。它从必要的资源(数据库, 网络服务调用, 等)中获取原始数据, 运用逻辑, 并处理成 view (controller) 的展示数据. 它(通常通过属性)暴露给视图控制器需要知道的仅关于显示视图工作的信息(理想地你不会暴漏你的 data-model 对象)。 它还负责对上游数据的修改(比如更新模型/数据库, API POST 调用)。
MVVM是基于胖Model的架构思路建立的,然后在胖Model中拆出两部分:Model和ViewModel。ViewModel本质上算是Model层(因为是胖Model里面分出来的一部分),所以View并不适合直接持有ViewModel,因为ViewModel有可能并不是只服务于特定的一个View,使用更加松散的绑定关系能够降低ViewModel和View之间的耦合度。
ViewController
尽量不涉及业务逻辑,让ViewModel
去做这些事情。ViewController
只是一个中间人,接收View
的事件、调用ViewModel
的方法、响应ViewModel
的变化。ViewModel
不能包含 View,不然就跟 View 产生了耦合,不方便复用和测试。ViewModel
之间可以有依赖。ViewModel
避免过于臃肿,不然维护起来也是个问题。
MVVM 并不复杂,跟 MVC 也是兼容的,只是多了一个 ViewModel 层,但就是这么一个小改动,就能让代码变得更加容易阅读和维护,不妨试一下吧。
怎么实现MVVM呢?接下来我将通过代码来演示一下我的具体实现过程。
model层
在这里我们用JSONModel来解析,比如一个商品列表的model长这样:
#import <JSONModel/JSONModel.h>
@protocol ProductListModel <NSObject>
@end
@interface ProductListModel : JSONModel
@property (nonatomic, copy) NSString *imgUrl;
@property (nonatomic, copy) NSString *productId;
@property (nonatomic, copy) NSString *productName;
@property (nonatomic, copy) NSString *refPrice;
@end
这是我们的数据原型,API返回的数据通过JSONModel解析完成后的原始数据存在这里。
viewModel 层
viewModel层是我们处理业务逻辑的核心层,在这里我们需要发起网络请求(如果网络请求较多,可以抽出来,只在ViewModel里调用)、解析数据、转换数据给前端。
- (void)startLoadProductListWithPage:(NSInteger)page {
__weak typeof(self) weakSelf = self;
[NetWorkManager GET:self.baseURL
parameters:parameters
success:^(NSURLSessionDataTask *task, id responseObject) {
__strong typeof(weakSelf) strongSelf = weakSelf;
...
NSDictionary *resultDic = responseObject[@"rp_result"];
NSError *error = nil;
ProductListModel *model = [[ProductListModel alloc] initWithDictionary:resultDic error:&error];
if (error) {
...
}
[strongSelf calProductLists:model.productlist];
if (strangles.delegate ...) {
...
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
...
}];
}
- (void)calProductLists:(NSArray *)productLists {
for (NSInteger i = 0; i < productLists.count; ++i) {
ProductListModel *model = productLists[i];
ProductListItem *item = [[ProductListItem alloc] init];
item.productId = model.productId;
item.productName = model.productName;
item.productPrice = [NSString stringWithFormat:@"¥ %@", model.refPrice];
item.productImgURL = [Utils convertToRealUrl:model.imgUrl ofsize:300];
[self.productLists addObject:item];
}
}
在viewModel中将API返回的数据解析成model,并将model转化成可供view层直接使用的item,将item交付给前端使用。
经过viewModel转化之后的数据item由viewModel保存,与数据相关的处理都将在viewModel中处理。viewModel返回给view层的接口长这样:
@interface ProductListViewModel (CollectionViewDataSource)
- (NSInteger)m_numberOfItemsInSection:(NSInteger)section;
- (ProductListItem *)m_itemForIndexPath:(NSIndexPath *)indexPath;
@end
view层
view层是由viewController控制的。view层只做展示,不做业务处理。view层的数据由viewModel提供。view层看起来是这样的:
@implementation ProductListViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
[self initial];
[self setupViewModel];
[self setupSubViews];
[self.viewModel startLoadProductListWithPage:_currentPage];
}
- (void)initial {
...
self.currentPage = 1;
}
- (void)setupViewModel {
self.viewModel = [[ProductListViewModel alloc] init];
_viewModel.delegate = self;
}
- (void)setupSubViews {
...
[self setupCollectionView];
...
}
- (void)setupCollectionView {
...
}
#pragma mark - UICollectionView Delegate & Datosource
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section {
return [self.viewModel m_numberOfItemsInSection:section];
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
ProductListItem *item = [self.viewModel m_itemForIndexPath:indexPath];
ProductListCollectionViewCell *cell = (ProductListCollectionViewCell *)[collectionView dequeueReusableCellWithReuseIdentifier:kProductListCollectionViewCellId forIndexPath:indexPath];
[cell setupWithItem:item];
return cell;
}
MVVM的缺点
转化过程的成本很大,主要在于:
1、数组内容的转化成本较高:数组里面每项都要转化成Item对象,如果Item对象中还有类似数组,就很头疼。
2、转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
3、只有在API返回的数据高度标准化时,这些对象原型(Item)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。
4、调试时通过对象原型查看数据内容不如直接通过NSDictionary/NSArray直观。
5、同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
针对这些缺点,也提出了相应的解决方法,即用一个类似reformer的对象进行数据过滤,根据不同的reformer对象过滤出不同的数据。
另外,这篇文章说得也很浅显易懂!