參考:
iOS 中MVC設(shè)計模式
iOS MVVM架構(gòu)
iOS MVVM-框架介紹
iOS 架構(gòu)模式MVVM的實(shí)踐總結(jié)
iOS MVVM+RAC 從框架到實(shí)戰(zhàn)
為什么使用MVVM
iOS中,我們使用的大部分都是MVC架構(gòu)。雖然MVC的層次明確,但是由于功能日益的增加、代碼的維護(hù),使得更多的代碼被寫在了Controller中,這樣Controller就顯得非常臃腫。
為了給Controller瘦身,后來又從MVC衍生出了一種新的架構(gòu)模式MVVM架構(gòu)。
MVVM分別指什么
MVVM就是在MVC的基礎(chǔ)上分離出業(yè)務(wù)處理的邏輯到ViewModel層,即:
Model層:請求的原始數(shù)據(jù)
View層:視圖展示,由ViewController來控制
ViewModel層:負(fù)責(zé)業(yè)務(wù)處理和數(shù)據(jù)轉(zhuǎn)化
簡單來說,就是API請求完數(shù)據(jù),解析成Model,之后在ViewModel中轉(zhuǎn)化成能夠直接被視圖層使用的數(shù)據(jù),交付給前端(View層)。
MVVM與MVC的不同
首先我們簡化一下MVC的架構(gòu)模式圖:

在這里,Controller需要做太多得事情,表示邏輯、業(yè)務(wù)邏輯,所以代碼量非常的大。而MVVM:

MVVM的實(shí)現(xiàn)
比如我們有一個需求:一個頁面,需要判斷用戶是否手動設(shè)置了用戶名。如果設(shè)置了,正常顯示用戶名;如果沒有設(shè)置,則顯示“簡書0122”這種格式。(雖然這些本應(yīng)是服務(wù)器端判斷的)
我們看看MVC和MVVM兩種架構(gòu)都是怎么實(shí)現(xiàn)這個需求的
MVC:
Model類:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId;
@end
@implementation User
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId {
self = [super init];
if (!self) return nil;
_userName = userName;
_userId = userId;
return self;
}
@end
ViewController類:
#import "HomeViewController.h"
#import "User.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) User *user;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
//創(chuàng)建User實(shí)例并初始化
if (_user.userName.length > 0) {
_lb_userName.text = _user.userName;
} else {
_lb_userName.text = [NSString stringWithFormat:@"簡書%ld", _user.userId];
}
}
@end
這里我們需要將表示邏輯也放在ViewController中。
MVVM:
Model類:
#import <Foundation/Foundation.h>
@interface User : NSObject
@property (nonatomic, copy) NSString *userName;
@property (nonatomic, assign) NSInteger userId;
@end
ViewModel類:
聲明:
#import <Foundation/Foundation.h>
#import "User.h"
@interface UserViewModel : NSObject
@property (nonatomic, strong) User *user;
@property (nonatomic, copy) NSString *userName;
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId;
@end
實(shí)現(xiàn):
#import "UserViewModel.h"
@implementation UserViewModel
- (instancetype)initWithUserName:(NSString *)userName userId:(NSInteger)userId {
self = [super init];
if (!self) return nil;
_user = [[User alloc] initWithUserName:userName userId:userId];
if (_user.userName.length > 0) {
_userName = _user.userName;
} else {
_userName = [NSString stringWithFormat:@"簡書%ld", _user.userId];
}
return self;
}
@end
Controller類:
#import "HomeViewController.h"
#import "UserViewModel.h"
@interface HomeViewController ()
@property (nonatomic, strong) UILabel *lb_userName;
@property (nonatomic, strong) UserViewModel *userViewModel;
@end
@implementation HomeViewController
- (void)viewDidLoad {
[super viewDidLoad];
_userViewModel = [[UserViewModel alloc] initWithUserName:@"liu" userId:123456];
_lb_userName.text = _userViewModel.userName;
}
@end
可見,Controller中我們不需要再做多余的判斷,那些表示邏輯我們已經(jīng)移植到了ViewModel中,ViewController明顯輕量了很多。說白了,就是把原來ViewController層的業(yè)務(wù)邏輯和頁面邏輯等剝離出來放到ViewModel層。
總結(jié):
- MVVM同MVC一樣,目的都是分離Model與View,但是它更好的將表示邏輯分離出來,減輕了Controller的負(fù)擔(dān);
- ViewController中不要引入Model,引入了就難免會在Controller中對Model做處理;
- 對于很簡單的界面使用MVVM會增加代碼量,但如果界面中內(nèi)容很多、Cell樣式也很多的情況下使用MVVM可以很好地將VC中處理Cell相關(guān)的工作分離出來。
寫到這里,MVVM基本上就算結(jié)束了。重要的還是去實(shí)踐,在實(shí)踐中檢驗(yàn)真理。
MVVM的缺點(diǎn)
關(guān)于MVVM的缺點(diǎn),Casa Taloyum大神在他的博客《iOS應(yīng)用架構(gòu)談 網(wǎng)絡(luò)層設(shè)計方案》中已經(jīng)闡述的很詳細(xì)了。這里簡單的引用原文做一個回顧。
這種做法是能夠提高后續(xù)操作代碼的可讀性的。在比較直覺的思路里面,是需要這部分轉(zhuǎn)化過程的,但這部分轉(zhuǎn)化過程的成本是很大的,主要成本在于:
數(shù)組內(nèi)容的轉(zhuǎn)化成本較高:數(shù)組里面每項都要轉(zhuǎn)化成Item對象,如果Item對象中還有類似數(shù)組,就很頭疼。
轉(zhuǎn)化之后的數(shù)據(jù)在大部分情況是不能直接被展示的,為了能夠被展示,還需要第二次轉(zhuǎn)化。
只有在API返回的數(shù)據(jù)高度標(biāo)準(zhǔn)化時,這些對象原型(Item)的可復(fù)用程度才高,否則容易出現(xiàn)類型爆炸,提高維護(hù)成本。
調(diào)試時通過對象原型查看數(shù)據(jù)內(nèi)容不如直接通過NSDictionary/NSArray直觀。
同一API的數(shù)據(jù)被不同View展示時,難以控制數(shù)據(jù)轉(zhuǎn)化的代碼,它們有可能會散落在任何需要的地方。
針對這些缺點(diǎn),Casa Taloyum大神也提出了相應(yīng)的解決方法,即用一個類似reformer
的對象進(jìn)行數(shù)據(jù)過濾,根據(jù)不同的reformer
對象過濾出不同的數(shù)據(jù)。這種方法,目前我也在進(jìn)行嘗試,之后我也會分享我的使用心得,敬請關(guān)注。
謝謝!