概要
MVC架構(gòu),Model-View-Controller,如圖一所示為一個(gè)典型的MVC設(shè)置。

- Model呈現(xiàn)數(shù)據(jù)
- View呈現(xiàn)用戶界面
- Controller調(diào)節(jié)兩者之間的交互。從Model取數(shù)據(jù),顯示在View中。
典型的MVC應(yīng)用里,許多邏輯被放在View Controller中,他們中一些確實(shí)屬于View Controller,但更多的是表現(xiàn)邏輯,即將Model中數(shù)據(jù)轉(zhuǎn)換為View可以呈現(xiàn)的內(nèi)容的事情。例如將JSON包里的某個(gè)NSDate轉(zhuǎn)換為特定格式的NSString。這也導(dǎo)致了MVC被人稱作Massive-View-Controller(重量級(jí)視圖控制器)。
通常Controller中應(yīng)該只放如下代碼:
- 初始化時(shí)構(gòu)造相應(yīng)的View和Model
- 監(jiān)聽Model層的事件,將Model層的數(shù)據(jù)傳遞到View層
- 監(jiān)聽View層的事件,將View層的事件傳遞到Model層
僅此而已,除此之外的任何邏輯都不應(yīng)該放到Controller中。因此這也就有了MVVM
MVVM
圖二所示為MVVM設(shè)置:MVVM其實(shí)就是MVC的增強(qiáng)版。我們正式連接了View 和View Controller,并將表示邏輯從Controller中移出,放到了一個(gè)新的對(duì)象里,即View Model中。

這樣做可帶來(lái)如下的益處:
- 減少View Controller的復(fù)雜性,使得表示邏輯易于測(cè)試。
- 兼容MVC模式
- MVVM 配合一個(gè)綁定機(jī)制效果最好
舉例
下面是一個(gè)看一個(gè)簡(jiǎn)單的 Person Model 以及相應(yīng)的 View Controller。
@interface Person : NSObject
- (instancetype)initwithSalutation:(NSString *)salutation firstName:(NSString *)firstName lastName:(NSString *)lastName birthdate:(NSDate *)birthdate;
@property (nonatomic, readonly) NSString *salutation;
@property (nonatomic, readonly) NSString *firstName;
@property (nonatomic, readonly) NSString *lastName;
@property (nonatomic, readonly) NSDate *birthdate;
@end
現(xiàn)在假設(shè)有一個(gè) PersonViewController,在 viewDidLoad里,只需要基于它的 model屬性設(shè)置一些Label即可。
- (void)viewDidLoad {
[super viewDidLoad];
if (self.model.salutation.length > 0) {
self.nameLabel.text = [NSString stringWithFormat:@"%@ %@ %@",self.model.salutation,self.model.firstName, self.model.lastName];
} else {
self.nameLabel.text = [NSString stringWithFormat:@"%@ %@", self.model.firstName,self.model.lastName];
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
self.birthdateLabel.text = [dateFormatter stringFromDate:model.birthdate];
}
現(xiàn)在來(lái)看怎樣通過(guò)一個(gè)ViewModel來(lái)增強(qiáng)它。
//ViewModel.h
@interface PersonViewModel : NSObject
- (instancetype)initWithPerson:(Person *)person;
@property (nonatomic, readonly) Person *person;
@property (nonatomic, readonly) NSString *nameText;
@property (nonatomic, readonly) NSString *birthdateText;
@end
ViewModel實(shí)現(xiàn)如下
//ViewModel.m
@implementation PersonViewModel
- (instancetype)initWithPerson:(Person *)person {
self = [super init];
if (!self) return nil;
_person = person;
if (person.salutation.length > 0) {
_nameText = [NSString stringWithFormat:@"%@ %@ %@", self.person.salutation, self.person.firstName, self.person.lastName];
} else {
_nameText = [NSString stringWithFormat:@"%@ %@", self.person.firstName, self.person.lastName];
}
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"EEEE MMMM d, yyyy"];
_birthdateText = [dateFormatter stringFromDate:person.birthdate];
return self;
}
@end
現(xiàn)在我們已將viewDidLoad中的表示邏輯放入我們的 View Model 里了。此時(shí),我們新的 viewDidLoad
就會(huì)非常輕量:
- (void)viewDidLoad { [super viewDidLoad];
self.nameLabel.text = self.viewModel.nameText;
self.birthdateLabel.text = self.viewModel.birthdateText;
}
MVVM作用與問(wèn)題
MVVM在實(shí)際使用時(shí)也有一定的問(wèn)題,主要體現(xiàn)在兩點(diǎn):
- 數(shù)據(jù)綁定使得 Bug 很難被調(diào)試。你看到界面異常了,有可能是你 View 的代碼有 Bug,也可能是 Model 的代碼有問(wèn)題。數(shù)據(jù)綁定使得一個(gè)位置的 Bug 被快速傳遞到別的位置,要定位原始出問(wèn)題的地方就變得不那么容易了。
- 對(duì)于過(guò)大的項(xiàng)目,數(shù)據(jù)綁定需要花費(fèi)更多的內(nèi)存。
ReactiveCocoa
MVVM引出了一個(gè)ReactiveCocoa,ReactiveCocoa作用如圖三所示:

如果想要深入了解ReactiveCocoa,可以看下ReactiveCocoa這篇文章。
備注:本文為讀書筆記,主要參考了如下幾篇文章: