前言
本著大道至簡,由淺入深的想法。本文會(huì)從一個(gè)簡單的例子入手,逐步解析MVVM在iOS中的應(yīng)用。說一說看法,比一比優(yōu)劣,如有不足之處,還望各路大神耐心指出,晚輩不勝感激!
文章目錄
- 架構(gòu)之爭
- MVVM初探
- 工程實(shí)踐
- 總結(jié)
一.架構(gòu)之爭
蘋果官方其實(shí)是推薦使用MVC,結(jié)構(gòu)大致如下:

可以看出
View跟Model事實(shí)上是沒有交互的,由Controller負(fù)責(zé)Model與View之間的交互,交互越多,Controller就越臃腫,更別提實(shí)際運(yùn)用中有些還去掉了View層或者Model層。目前對MVC架構(gòu)劃分是Model作為數(shù)據(jù)管理者,View作為數(shù)據(jù)展示者,Controller作為數(shù)據(jù)加工者。
然而在iOS中Controller中由于有蘋果內(nèi)定的一些視圖的生命周期在里面,比如viewDidLoad等等,于是就出現(xiàn)了一些關(guān)于iOS的MVC架構(gòu)方面的爭論,有些認(rèn)為在iOS開發(fā)中并沒有什么View和Controller,只有Model+ViewController;個(gè)人比較推崇Casa Taloyum的劃分:
M應(yīng)該做的事:
1.給ViewController提供數(shù)據(jù)
2.給ViewController存儲(chǔ)數(shù)據(jù)提供接口
3.提供經(jīng)過抽象的業(yè)務(wù)基本組件,供Controller調(diào)度
C應(yīng)該做的事:
1.管理View Container的生命周期
2.負(fù)責(zé)生成所有的View實(shí)例,并放入View Container
3.監(jiān)聽來自View與業(yè)務(wù)有關(guān)的事件,通過與Model的合作,來完成對應(yīng)事件的業(yè)務(wù)。
V應(yīng)該做的事:
1.響應(yīng)與業(yè)務(wù)無關(guān)的事件,并因此引發(fā)動(dòng)畫效果,點(diǎn)擊反饋(如果合適的話,盡量還是放在View去做)等。
2.界面元素表達(dá)
嚴(yán)格意義來說Controller確實(shí)做了視圖相關(guān)的操作,但這個(gè)是蘋果封裝給開發(fā)者的視圖容器,暴露一些模板方法方便調(diào)用,我們應(yīng)該是在這個(gè)基礎(chǔ)上進(jìn)行iOS的MVC架構(gòu)開發(fā)吧??(PS:個(gè)人看法,隨便嘮嘮);
至于從MVC演變過來的MVVM,則做了進(jìn)一步的優(yōu)化:

抽出了
ViewModel層負(fù)責(zé)數(shù)據(jù)與視圖的交互部分,Controller僅協(xié)調(diào)各個(gè)部分的綁定關(guān)系以及必要的邏輯處理,具體各個(gè)模塊之間的分配借用ReactiveCocoa和MVVM,簡介的一張圖:
介紹到這里,想必大家對MVC和MVVM有了一些基本的了解,具體要用什么架構(gòu)大家各取所需,真正實(shí)現(xiàn)所選架構(gòu)。
小結(jié)一下
介紹一下兩者對比結(jié)果:
MVC
優(yōu)點(diǎn):
通用架構(gòu);
處理耦合度高的邏輯方便;
缺點(diǎn):
耦合度高;
復(fù)用性差;
測試性差;
MVVM
優(yōu)點(diǎn):
耦合度低;
復(fù)用性高;
測試性高;
層次更清晰;
重構(gòu)成本低;
缺點(diǎn):
處理耦合度高的邏輯比較復(fù)雜;
若加入RAC,增加學(xué)習(xí)成本;
一些Bug比較難調(diào)試;
二.MVVM初探
筆者選用RAC實(shí)現(xiàn)MVVM架構(gòu),當(dāng)然不是必要的,重要的實(shí)現(xiàn)架構(gòu),用到的一些庫都算是工具,也可以自己用KVO實(shí)現(xiàn),原生的KVO實(shí)現(xiàn)會(huì)遇到一些iOS下KVO使用過程中的陷阱,還有諸如父類被子類KVO方法覆蓋,收到監(jiān)聽消息的判斷過于冗長等等;這里推薦使用Facebook開源的KVOController 框架,跟示例差不多,就不重復(fù)列舉了;
好了,??下面開始寫代碼了~
工程結(jié)構(gòu)如下:

2.1 ZBMVVMSimpleViewController
協(xié)調(diào)viewModel綁定model,view綁定viewModel;
- (void)viewDidLoad
{
[super viewDidLoad];
//初始化
self.simpleModel.name = @"帥斌";
//創(chuàng)建視圖
[self.view addSubview:self.simpleView];
/*綁定關(guān)系*/
//viewModel綁定model
[self.simpleViewModel bindModel:self.simpleModel];
//view綁定viewModel
[self.simpleView bindViewModel:self.simpleViewModel];
}
2.2 ZBMVVMSimpleView
創(chuàng)建視圖,實(shí)現(xiàn)綁定viewModel的內(nèi)部邏輯;
- (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;
}
//按鈕點(diǎn)擊方法
- (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部分:
對外暴露的一些可供調(diào)用的接口:
@interface ZBMVVMSimpleViewModel : NSObject
@property (nonatomic, strong) NSString *nameStr;
//綁定model
- (void)bindModel:(id)model;
//按鈕點(diǎn)擊方法的實(shí)現(xiàn)
- (void)changeButtonTextAction;
@end
ZBMVVMSimpleViewModel.m部分:
實(shí)現(xiàn)綁定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;
}
//按鈕點(diǎn)擊方法的實(shí)現(xiàn)
- (void)changeButtonTextAction
{
_isClick = !_isClick;
if(_isClick){
self.model.name = @"火之玉";
}else{
self.model.name = @"帥斌";
}
self.nameStr = self.model.name;
}
@end
通過這個(gè)簡單的案例,可以看出MVVM各個(gè)部分之間的關(guān)系以及如何實(shí)現(xiàn)這一架構(gòu);
MVVM的Model和View沒有交互,交互移步到ViewModel;View持有ViewModel,ViewModel持有Model,反過來持有的話View容易直接跟Model容易產(chǎn)生耦合,這樣就失去了架構(gòu)的意義;
小結(jié)一下:
MVVM的核心在于:(個(gè)人意見)
1.MVVM的雙向綁定;
2.Model與View解耦;
三.工程實(shí)踐
參照iOS MVVM+RAC 從框架到實(shí)戰(zhàn)自己實(shí)現(xiàn)了個(gè)小demo:

看了網(wǎng)上一些實(shí)現(xiàn)案例,最后選擇了這種結(jié)構(gòu)清晰,又方便管理的工程模式,順便告訴作者一句,已點(diǎn)贊,已star。
這里原本想抽出tableView的dataSource父類,但看到網(wǎng)上一個(gè)比較好的案例,基于MVVM,用于快速搭建設(shè)置頁,個(gè)人信息頁的框架,也挺有意思的,學(xué)習(xí)了。
說一下發(fā)現(xiàn)的一個(gè)小問題,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是一個(gè)局部變量,在方法調(diào)用后就會(huì)釋放,不存在相互持有,所以這里可以不用weak的;
總結(jié)
MVVM模式一直是熱議的話題,在眾多語言里都有被模仿。雖然將View和Model分離了,但是也增加了數(shù)據(jù)綁定,數(shù)據(jù)分離的一些代碼??傊欣斜装?,供開發(fā)者自由選擇。ViewController要想瘦身不光一種模式可以選擇,實(shí)際開發(fā)過程中,可能工程模式已經(jīng)固定,需要一步步進(jìn)行代碼優(yōu)化,一下子轉(zhuǎn)MVVM還真的有些困難。唐巧大神在被誤解的 MVC 和被神化的 MVVM提供了幾個(gè)ViewController瘦身的思路,值得借鑒。
好了,這次MVVM就分享這么多,以后還會(huì)分享更深入更有意思的內(nèi)容,歡迎探討~
最后附上工程鏈接:
點(diǎn)此下載
博文推薦:
iOS應(yīng)用架構(gòu)談 view層的組織和調(diào)用方案
ReactiveCocoa 和 MVVM 入門
被誤解的 MVC 和被神化的 MVVM
iOS MVVM+RAC 從框架到實(shí)戰(zhàn)
猿題庫 iOS 客戶端架構(gòu)設(shè)計(jì)
MVVM奇葩說