iOS 关于MVC和MVVM设计模式的那些事
一、概述
-
在 iOS 开发中,MVC(Model View Controller)是构建iOS App的标准模式,是苹果推荐的一个用来组织代码的权威范式。Apple甚至是这么说的。在MVC下,所有的对象被归类为一个Model,一个View,和一个Controller。Model持有数据,View显示与用户交互的界面,而ViewController调解Model和View之间的交互。现在,MVC 依然是目前主流客户端编程框架,但同时它也被调侃成Massive View Controller(重量级视图控制器),想必开发者在开发中无可避免被下面几个问题所困扰:
- 厚重的ViewController
- 遗失的网络逻辑(无立足之地)
- 较差的可测试性
-
为了避免和解决上述问题的产生,从MVC引申出来一种维护性较强、耦合性低的新的架构MVVM(Model View View-Mode),MVVM正式规范了视图和控制器紧耦合的性质,并引入新的组件。MVVM主要目的是为了分离视图(View)和模型(Model)。
-
本文只是分享一下笔者对MVC和MVVM的一些见解,在此抛砖引玉,希望能为存在对MVC和MVVM迷茫的广大开发者提供一点思路,少走一些弯路,填补一些细坑。文章仅供大家参考,若有不妥之处,还望不吝赐教,欢迎批评指正。
二、MVC(Model View Controller)
- MVC之间的关系
任何一个正经开发过软件的人都熟悉MVC
,它意思是Model View Controller, 是一个在复杂应用设计中组织代码的公认模式,它们之间的结构关系如下:
MVC示意图.png
我们看到的只是一个苹果 **典型的MVC ** 设置。view
将用户交互通知给controller
。view controller
通过更新model
来反应状态的改变。model
(通常使用Key-Value-Observation
)通知controller
来更新他们负责的view
。大多数iOS应用程序的代码使用这种方式来组织。然而,典型的MVC架构
不适用于当下的iOS开发。尽管从技术上看View
和 Controller
是相互独立的,但事实上它们几乎总是结对出现,一个 View
只能与一个 Controller
进行匹配,反之亦然。既然如此,那我们为何不正规化它们的连接:
因此,M-VC 可能是对 iOS 开发中的 MVC
模式更为准确的解读,同时更也准确地描述了我们日常开发可能已经编写的 MVC
代码,但它并没有做太多事情来解决 iOS 应用中日益增长的重量级视图控制器的问题。(PS:躺枪了没...)
举例说明:
M-VC_Example.png
若假设笔者利用MVC
的设计模式来开发此界面,那想必是这样的。
M:SUGoods(商品模型Model)
V:SUGoodsCell(展示商品数据的View,自定义的 UITableViewCell)
C:SUHomeViewController (首页控制器Controller)
控制器(SUHomeViewController
)代码实现
- (void) requestRemoteData
{
// 1.发起网络请求,获取到服务器的数据,并将其转化成模型数据(`SUGoods`)
// 2.添加到数据源(`dataSource`)
// 3.最后刷新表格`[self.tableView reloadData]`,配置`SUGoodsCel`l即可
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SUGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Goods"];
cell.goods = self.dataSource[indexPath.row];
return cell;
}
View(SUGoodsCell)
代码实现
SUGoodsCell.h
@class SUGoods;
@interface SUGoodsCell : UITableViewCell
@property (nonatomic, strong) SUGoods *goods;
@end
SUGoodsCell.m
@implementation SUGoodsCell
- (void)setGoods:(SUGoods *)goods
{
_goods = goods
/// config data ....
}
@end
都写到这份上了,大家用脚趾头想想,这个SUGoodsCell
,正是由View
直接来调用Model
,所以事实上典型的MVC
的原则已经违背了,但是这种情况是一直发生的甚至于人们不觉得这里有哪些不对。如果严格遵守MVC
的话,你会把对cell
的设置放在Controller
中,不向View
传递一个Model
对象,这样就会大大增加Controller的体积
。所以说,这哪里是典型的MVC
设计模式,这分明是M-VC
设计模式呀。简而言之,在理想的世界里,MVC
也许工作的很好。然而,我们生活在真实的世界,谢谢(PS:让梦想实现的最好的方式,就是醒来!!!)。
- MVC的弊端
MVC
的利弊大家想必是有目共睹的,Massive View Controller
的说法也并非空穴来风的。让我们一起探讨MVC
的弊端,剖析问题产生原因,打造一个轻量级的ViewController
,明确MVC
设计模式中各个角色的职责。
-
厚重的View Controller
M:模型model的对象通常非常的简单。根据Apple的文档,model
应包括数据
和操作数据的业务逻辑
。而在实践中,model
层往往非常薄,不管怎样,model
层的业务逻辑不应被拖入到controller
。
V:视图view
通常是UIKit
控件(component
,这里根据习惯译为控件)或者编码定义的UIKit
控件的集合。View
的如何构建(PS:IB或者手写界面
)何必让Controller
知晓,同时View
不应该直接引用model
(PS:现实中,你懂的!),并且仅仅通过IBAction事件
引用controller
。业务逻辑很明显不归入view
,视图本身没有任何业务。
C:控制器controller
。Controller
是app的“胶水代码”:协调模型和视图之间的所有交互。控制器负责管理他们所拥有的视图的视图层次结构,还要响应视图的loading
、appearing
、disappearing
等等,同时往往也会充满我们不愿暴露的model的模型逻辑
以及不愿暴露给视图的业务逻辑
。
网络数据的请求及后续处理,本地数据库操作,以及一些带有工具性质辅助方法都加大了Massive View Controller
的产生。 -
遗失(无处安放)的网络逻辑
苹果使用的MVC
的定义是这么说的:所有的对象都可以被归类为一个model
,一个view
,或是一个controller
。
你可能试着把它放在Model
对象里,但是也会很棘手,因为网络调用应该使用异步,这样如果一个网络请求比持有它的model
生命周期更长,事情将变的复杂。显然View
里面做网络请求那就更格格不入了,因此只剩下Controller
了。若这样,这又加剧了Massive View Controller
的问题。若不这样,何处才是网络逻辑
的家呢? -
较差的可测试性
由于View Controller
混合了视图处理逻辑和业务逻辑,分离这些成分的单元测试成了一个艰巨的任务。若一个Massive View Controller
有上万行代码,要你编写个单元测试,我敢保证,你不是想写,你是想死,分分钟填表走人。
三、MVVM(Model View View-Mode)
一种可以很好地解决Massive View Controller
问题的办法就是将 Controller
中的展示逻辑抽取出来,放置到一个专门的地方,而这个地方就是 viewModel
。MVVM
衍生于MVC
,是对 MVC
的一种演进,它促进了 UI 代码与业务逻辑的分离。它正式规范了视图和控制器紧耦合的性质,并引入新的组件。他们之间的结构关系如下:
-
MVVM 的基本概念
- 在
MVVM
中,view
和view controller
正式联系在一起,我们把它们视为一个组件 -
view
和view controller
都不能直接引用model
,而是引用视图模型(viewModel
) -
viewModel
是一个放置用户输入验证逻辑,视图显示逻辑,发起网络请求和其他代码的地方 - 使用
MVVM
会轻微的增加代码量,但总体上减少了代码的复杂性
- 在
-
MVVM 的注意事项
-
view
引用viewModel
,但反过来不行(即不要在viewModel
中引入#import UIKit.h
,任何视图本身的引用都不应该放在viewModel
中)(PS:基本要求,必须满足) -
viewModel
引用model
,但反过来不行
-
-
MVVM 的使用建议
-
MVVM
可以兼容你当下使用的MVC
架构。 -
MVVM
增加你的应用的可测试性。 -
MVVM
配合一个绑定机制效果最好(PS:ReactiveCocoa你值得拥有)。 -
viewController
尽量不涉及业务逻辑,让viewModel
去做这些事情。 -
viewController
只是一个中间人,接收view
的事件、调用viewModel
的方法、响应viewModel
的变化。 -
viewModel
绝对不能包含视图view(UIKit.h)
,不然就跟view
产生了耦合,不方便复用和测试。 -
viewModel
之间可以有依赖。 -
viewModel
避免过于臃肿,否则重蹈Controller
的覆辙,变得难以维护。
-
-
MVVM 的优势
- 低耦合:
View
可以独立于Model
变化和修改,一个viewModel
可以绑定到不同的View
上 - 可重用性:可以把一些视图逻辑放在一个
viewModel
里面,让很多view
重用这段视图逻辑 - 独立开发:开发人员可以专注于业务逻辑和数据的开发
viewModel
,设计人员可以专注于页面设计 - 可测试:通常界面是比较难于测试的,而
MVVM
模式可以针对viewModel
来进行测试
- 低耦合:
-
MVVM 的弊端
- 数据绑定使得
Bug
很难被调试。你看到界面异常了,有可能是你View
的代码有Bug
,也可能是Model
的代码有问题。数据绑定使得一个位置的Bug
被快速传递到别的位置,要定位原始出问题的地方就变得不那么容易了。 - 对于过大的项目,数据绑定和数据转化需要花费更多的内存(成本)。主要成本在于:
- 数组内容的转化成本较高:数组里面每项都要转化成
Item
对象,如果Item对象中还有类似数组,就很头疼。 - 转化之后的数据在大部分情况是不能直接被展示的,为了能够被展示,还需要第二次转化。
- 只有在API返回的数据高度标准化时,这些对象原型(
Item
)的可复用程度才高,否则容易出现类型爆炸,提高维护成本。 - 调试时通过对象原型查看数据内容不如直接通过
NSDictionary/NSArray
直观。 - 同一API的数据被不同View展示时,难以控制数据转化的代码,它们有可能会散落在任何需要的地方。
- 数组内容的转化成本较高:数组里面每项都要转化成
- 数据绑定使得
四、总结
-
MVC
的设计模式也并非是病入膏肓,无药可救的架构,最起码目前MVC
设计模式仍旧是iOS开发的主流框架,存在即合理。针对文章所述的弊端,我们依旧有许多可行的方法去避免和解决,从而打造一个轻量级的ViewController
。 -
MVVM
是MVC
的升级版,完全兼容当前的MVC
架构,MVVM
虽然促进了UI 代码与业务逻辑的分离,一定程度上减轻了ViewController
的臃肿度,但是View
和ViewModel
之间的数据绑定使得MVVM
变得复杂和难用了,如果我们不能更好的驾驭两者之间的数据绑定,同样会造成Controller
代码过于复杂,代码逻辑不易维护的问题。 - 一个轻量级的
ViewController
是基于MVC
和MVVM
模式进行代码职责的分离而打造的。MVC
和MVVM
有优点也有缺点,但缺点在他们所带来的好处面前时不值一提的。他们的低耦合性,封装性,可测试性,可维护性和多人协作便利大大提高了开法效率。 - 同时,我们需要保持的是一个拥抱变化的心,以及理性分析的态度。在新技术的面前,不盲从,也不守旧,一切的决策都应该建立在认真分析的基础上,这样才能应对技术的变化。
五、期待
- 文章若对您有点帮助,请给个喜欢❤️,毕竟码字不易;若对您没啥帮助,请给点建议💗,切记学无止境。
- 针对文章所述内容,阅读期间任何疑问;请在文章底部批评指正,我会火速解决和修正问题。
- GitHub地址:https://github.com/CoderMikeHe
六、实战篇
七、参考链接
- http://www.cnblogs.com/brycezhang/p/3840567.html
- http://www.mamicode.com/info-detail-989164.html
- http://blog.devtang.com/2015/11/02/mvc-and-mvvm/
- https://objccn.io/issue-13-1/
- http://www.cocoachina.com/ios/20160108/14916.html
- https://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html
- http://www.cocoachina.com/ios/20151020/13795.html