前言
本着大道至简,由浅入深的想法。本文会从一个简单的例子入手,逐步解析MVVM在iOS中的应用。说一说看法,比一比优劣,如有不足之处,还望各路大神耐心指出,晚辈不胜感激!
文章目录
- 架构之争
- MVVM初探
- 工程实践
- 总结
一.架构之争
苹果官方其实是推荐使用MVC,结构大致如下:
可以看出
View
跟Model
事实上是没有交互的,由Controller
负责Model
与View
之间的交互,交互越多,Controller
就越臃肿,更别提实际运用中有些还去掉了View
层或者Model
层。目前对MVC架构划分是Model
作为数据管理者
,View
作为数据展示者
,Controller
作为数据加工者
。
然而在iOS中Controller
中由于有苹果内定的一些视图的生命周期在里面,比如viewDidLoad
等等,于是就出现了一些关于iOS的MVC架构方面的争论,有些认为在iOS开发中并没有什么View
和Controller
,只有Model+ViewController
;个人比较推崇Casa Taloyum的划分:
M应该做的事:
1.给ViewController提供数据
2.给ViewController存储数据提供接口
3.提供经过抽象的业务基本组件,供Controller调度
C应该做的事:
1.管理View Container的生命周期
2.负责生成所有的View实例,并放入View Container
3.监听来自View与业务有关的事件,通过与Model的合作,来完成对应事件的业务。
V应该做的事:
1.响应与业务无关的事件,并因此引发动画效果,点击反馈(如果合适的话,尽量还是放在View去做)等。
2.界面元素表达
严格意义来说Controller
确实做了视图相关的操作,但这个是苹果封装给开发者的视图容器,暴露一些模板方法方便调用,我们应该是在这个基础上进行iOS的MVC架构开发吧😝(PS:个人看法,随便唠唠);
至于从MVC演变过来的MVVM,则做了进一步的优化:
抽出了
ViewModel
层负责数据与视图的交互部分,Controller
仅协调各个部分的绑定关系以及必要的逻辑处理,具体各个模块之间的分配借用ReactiveCocoa和MVVM,简介的一张图:介绍到这里,想必大家对MVC和MVVM有了一些基本的了解,具体要用什么架构大家各取所需,真正实现所选架构。
小结一下
介绍一下两者对比结果:
MVC
优点:
通用架构;
处理耦合度高的逻辑方便;
缺点:
耦合度高;
复用性差;
测试性差;
MVVM
优点:
耦合度低;
复用性高;
测试性高;
层次更清晰;
重构成本低;
缺点:
处理耦合度高的逻辑比较复杂;
若加入RAC,增加学习成本;
一些Bug比较难调试;
二.MVVM初探
笔者选用RAC实现MVVM架构,当然不是必要的,重要的实现架构,用到的一些库都算是工具,也可以自己用KVO实现,原生的KVO实现会遇到一些iOS下KVO使用过程中的陷阱,还有诸如父类被子类KVO方法覆盖,收到监听消息的判断过于冗长等等;这里推荐使用Facebook开源的KVOController 框架,跟示例差不多,就不重复列举了;
好了,👇下面开始写代码了~
工程结构如下:
2.1 ZBMVVMSimpleViewController
协调viewModel绑定model,view绑定viewModel;
- (void)viewDidLoad
{
[super viewDidLoad];
//初始化
self.simpleModel.name = @"帅斌";
//创建视图
[self.view addSubview:self.simpleView];
/*绑定关系*/
//viewModel绑定model
[self.simpleViewModel bindModel:self.simpleModel];
//view绑定viewModel
[self.simpleView bindViewModel:self.simpleViewModel];
}
2.2 ZBMVVMSimpleView
创建视图,实现绑定viewModel的内部逻辑;
- (instancetype)init
{
self = [super init];
if(self){
self.frame = [UIScreen mainScreen].bounds;
self.backgroundColor = [UIColor whiteColor];
self.nameButton = [UIButton buttonWithType:UIButtonTypeSystem];
_nameButton.frame = CGRectMake(0, 0, 100, 50);
_nameButton.center = CGPointMake(self.frame.size.width / 2.0, (self.frame.size.height / 3.0 * 1));
_nameButton.backgroundColor = [UIColor blackColor];
[_nameButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[_nameButton addTarget:self action:@selector(nameButtonAction) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:_nameButton];
}
return self;
}
//按钮点击方法
- (void)nameButtonAction
{
if(self.viewModel){
[self.viewModel changeButtonTextAction];
}
}
//绑定viewModel
- (void)bindViewModel:(id)viewModel
{
self.viewModel = viewModel;
@weakify(self);
[[RACObserve(self.viewModel, nameStr) ignore:nil] subscribeNext:^(id _Nullable x) {
@strongify(self);
[self.nameButton setTitle:x forState:UIControlStateNormal];
}];
}
2.3 ZBMVVMSimpleViewModel
ZBMVVMSimpleViewModel.h
部分:
对外暴露的一些可供调用的接口:
@interface ZBMVVMSimpleViewModel : NSObject
@property (nonatomic, strong) NSString *nameStr;
//绑定model
- (void)bindModel:(id)model;
//按钮点击方法的实现
- (void)changeButtonTextAction;
@end
ZBMVVMSimpleViewModel.m
部分:
实现绑定model,按钮更换name;
@interface ZBMVVMSimpleViewModel()
@property (nonatomic, strong) ZBMVVMSimpleModel *model;
@property (nonatomic, assign) BOOL isClick;
@end
@implementation ZBMVVMSimpleViewModel
//绑定model
- (void)bindModel:(id)model
{
self.model = model;
self.nameStr = self.model.name;
}
//按钮点击方法的实现
- (void)changeButtonTextAction
{
_isClick = !_isClick;
if(_isClick){
self.model.name = @"火之玉";
}else{
self.model.name = @"帅斌";
}
self.nameStr = self.model.name;
}
@end
通过这个简单的案例,可以看出MVVM各个部分之间的关系以及如何实现这一架构;
MVVM的Model
和View
没有交互,交互移步到ViewModel
;View
持有ViewModel
,ViewModel
持有Model
,反过来持有的话View
容易直接跟Model
容易产生耦合,这样就失去了架构的意义;
小结一下:
MVVM的核心在于:(个人意见)
1.MVVM的双向绑定;
2.Model
与View
解耦;
三.工程实践
参照iOS MVVM+RAC 从框架到实战自己实现了个小demo:
看了网上一些实现案例,最后选择了这种结构清晰,又方便管理的工程模式,顺便告诉作者一句,已点赞,已star。
这里原本想抽出tableView
的dataSource
父类,但看到网上一个比较好的案例,基于MVVM,用于快速搭建设置页,个人信息页的框架,也挺有意思的,学习了。
说一下发现的一个小问题,Masonry
的block
中使用了weak
;
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
通过源码可以看出Masonry
的block是一个局部变量,在方法调用后就会释放,不存在相互持有,所以这里可以不用weak
的;
总结
MVVM模式一直是热议的话题,在众多语言里都有被模仿。虽然将View
和Model
分离了,但是也增加了数据绑定,数据分离的一些代码。总之有利有弊吧,供开发者自由选择。ViewController
要想瘦身不光一种模式可以选择,实际开发过程中,可能工程模式已经固定,需要一步步进行代码优化,一下子转MVVM还真的有些困难。唐巧大神在被误解的 MVC 和被神化的 MVVM提供了几个ViewController
瘦身的思路,值得借鉴。
好了,这次MVVM就分享这么多,以后还会分享更深入更有意思的内容,欢迎探讨~
最后附上工程链接:
点此下载
博文推荐:
iOS应用架构谈 view层的组织和调用方案
ReactiveCocoa 和 MVVM 入门
被误解的 MVC 和被神化的 MVVM
iOS MVVM+RAC 从框架到实战
猿题库 iOS 客户端架构设计
MVVM奇葩说