一:为什么要用MVVM?
为什么要用MVVM?只是因为它不会让我时常懵逼。
每次做完项目过后,都会被自己庞大的ViewController代码吓坏,不管是什么网络请求、networking data process、跳转交互逻辑统统往ViewController里面塞,就算是自己写的代码,也不敢直视。我不得不思考是不是MVC模式太过落后了,毕竟它叫做Massive View Controller,其实说MVC落后不太合理,说它太原生了比较合适。
MVC模式的历史非常的久远,它其实不过是对编程模式的一种模块化,不管是MVVM、MVCS、还是听起来就毛骨悚然的VIPER,都是对MVC标准的三个模块的继续划分,细分下去,使每个模块的功能更加的独立和单一,而最终目的都是为了提升代码的规范程度,解耦,和降低维护成本。具体用什么模式需要根据项目的需求来决定,而这里,我简单的说说自己对MVVM架构的理解和设计思想,浅谈拙见。
二:MVVM模块划分
传统的MVC模式分为:Model、View、Controller。Model是数据模型,有胖瘦之分,View负责界面展示,而Controller就负责剩下的逻辑和业务,瞬间Controller心中一万个草泥马奔腾而过。
MVVM模式只是多了一个ViewModel,它的作用是为Controller减负,将Controller里面的逻辑(主要是弱业务逻辑)转移到自身,其实它涉及到的工作不止是这些,还包括页面展示数据的处理等。(后序章节会有具体讲解)
![](https://ask.qcloudimg.com/http-save/yehe-1729668/sl3bfgxrhy.png?imageView2/0/w/1620)
我的设计是这样的:
- 一个View对应一个ViewModel,View界面元素属性与ViewModel处理后的数据属性绑定
- Model只是在有网络数据的时候需要创建,它的作用只是一个数据的中专站,也就是一个极为简介的瘦model
- 这里弱化了Model的作用,而将对网络数据的处理的逻辑放在ViewModel中,也就是说,只有在有网络数据展示的View的ViewModel中,才会看见Model的影子,而处理过后的数据,将变成ViewModel的属性,注意一点,这些属性一定要尽量“直观”,比如能写成UIImage就不要写成URL
- ViewModel和Model可以视情况看是否需要属性绑定
- Controller的作用就是将主View通过与之对应的ViewModel初始化,然后添加到self.view,然后就是监听跳转逻辑触发等少部分业务逻辑,当然,ViewController的跳转还是需要在这里实现。 注意:这里面提到的绑定,其实就是对属性的监听,当属性变化时,监听者做一些逻辑处理,强大的框架来了————RAC
三:ReactiveCocoa
RAC是一个强大的工具,它和MVVM模式的结合使用只能用一个词形容————完美。
当然,有些开发者不太愿意用这些东西,大概是因为他们觉得这破坏了代理、通知、监听、block等的复杂逻辑观感,但是我在这里大力推崇RAC,因为我的MVVM搭建思路里面会涉及大量的属性绑定、事件传递,我可不想写上一万个协议来实现这些简单的功能,运用RAC能大量简化代码,使逻辑更加的清晰。
接下来我将对我的MVVM架构实现思路做一个详细的讲解,在这之前,如果你没有用过RAC,请先移步:
![](https://ask.qcloudimg.com/http-save/yehe-1729668/yvrzeopk9x.png?imageView2/0/w/1620)
大致的了解一下RAC过后,便可以往下(^)
四:MVVM模块具体实现
这是要实现的界面:
![](https://ask.qcloudimg.com/http-save/yehe-1729668/lb5oyb1mjk.png?imageView2/0/w/1620)
AF45BFF3B07B52D222AF90AE1CCBAC18.png
1、Model
这里我弱化了Model的作用,它只是作为一个网络请求数据的中转站,只有在View需要显示网络数据的时候,对应的ViewModel里面才有Model的相关处理。
2、ViewModel
在实际开发当中,一个View对应一个ViewModel,主View对应并且绑定一个主ViewModel。
主ViewModel承担了网络请求、点击事件协议、初始化子ViewModel并且给子ViewModel的属性赋初值;网络请求成功返回数据过后,主ViewModel还需要给子ViewModel的属性赋予新的值。
主ViewModel的观感是这样的:
@interface MineViewModel : NSObject
//viewModel
@property (nonatomic, strong) MineHeaderViewModel *mineHeaderViewModel;
@property (nonatomic, strong) NSArray<MineTopCollectionViewCellViewModel *> *dataSorceOfMineTopCollectionViewCell;
@property (nonatomic, strong) NSArray<MineDownCollectionViewCellViewModel *> *dataSorceOfMineDownCollectionViewCell;
//RACCommand
@property (nonatomic, strong) RACCommand *autoLoginCommand;
//RACSubject
@property (nonatomic, strong) RACSubject *pushSubject;
@end
其中,RACCommand是放网络请求的地方,RACSubject相当于协议,这里用于点击事件的代理,而ViewModel下面的一个ViewModel属性和三个装有ViewModel的数组我需要着重说一下。
在iOS开发中,我们通常会自定义View,而自定义的View有可能是继承自UICollectionviewCell(UITableViewCell、UITableViewHeaderFooterView等),当我们自定义一个View的时候,这个View不需要复用且只有一个,我们就在主ViewModel声明一个子ViewModel属性,当我们自定义一个需要复用的cell、item、headerView等的时候,我们就在主ViewModel中声明数组属性,用于储存复用的cell、item的ViewModel,中心思想仍然是一个View对应一个ViewModel。
在.m文件中,对这些属性做懒加载处理,并且将RACCommand和RACSubject配置好,方便之后在需要的时候触发以及调用,代码如下:
@implementation MineViewModel
- (instancetype)init
{
self = [super init];
if (self) {
[self initialize];
}
return self;
}
- (void)initialize {
[self.autoLoginCommand.executionSignals.switchToLatest subscribeNext:^(id responds) {
//处理网络请求数据
......
}];
}
#pragma mark *** getter ***
- (RACSubject *)pushSubject {
if (!_pushSubject) {
_pushSubject = [RACSubject subject];
}
return _pushSubject;
}
- (RACCommand *)autoLoginCommand {
if (!_autoLoginCommand) {
_autoLoginCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
return [RACSignal createSignal:^RACDisposable *(id<RACSubscriber> subscriber) {
NSDictionary *paramDic = @{......};
[Network start:paramDic success:^(id datas) {
[subscriber sendNext:datas];
[subscriber sendCompleted];
} failure:^(NSString *errorMsg) {
[subscriber sendNext:errorMsg];
[subscriber sendCompleted];
}];
return nil;
}];
}];
}
return _autoLoginCommand;
}
- (MineHeaderViewModel *)mineHeaderViewModel {
if (!_mineHeaderViewModel) {
_mineHeaderViewModel = [MineHeaderViewModel new];
_mineHeaderViewModel.headerBackgroundImage = [UIImage imageNamed:@"BG"];
_mineHeaderViewModel.headerImageUrlStr = nil;
[[[RACObserve([LoginBackInfoModel shareLoginBackInfoModel], headimg) distinctUntilChanged] takeUntil:self.rac_willDeallocSignal] subscribeNext:^(id x) {
if (x == nil) {
_mineHeaderViewModel.headerImageUrlStr = nil;
} else {
_mineHeaderViewModel.headerImageUrlStr = x;
}
}];
......
return _mineHeaderViewModel;
}
- (NSArray<MineTopCollectionViewCellViewModel *> *)dataSorceOfMineTopCollectionViewCell {
if (!_dataSorceOfMineTopCollectionViewCell) {
MineTopCollectionViewCellViewModel *model1 = [MineTopCollectionViewCellViewModel new];
MineTopCollectionViewCellViewModel *model2 = [MineTopCollectionViewCellViewModel new];
......
_dataSorceOfMineTopCollectionViewCell = @[model1, model2];
}
return _dataSorceOfMineTopCollectionViewCell;
}
- (NSArray<MineDownCollectionViewCellViewModel *