_翻譯自 _:Introduction to MVVM
(注:沒有完全翻譯全文,只是翻譯了和MVVM真正相關的部分)
什么是MVVM?

上圖演示了典型的MVC架構(gòu)。Model展示數(shù)據(jù),view展示用戶界面,ViewController調(diào)解兩者之間的交互。
思考片刻,盡管view和viewController嚴格來講是不同的組件,但是他們總是配對,手牽手的在一起。那么為什么不正式確立兩者的關系?

如此一來能更精確地描述你可能已經(jīng)寫過的MVC代碼。但是這并不能處理iOS中不斷增長的臃腫的視圖控制器。通常MVC應用,很多邏輯都放在視圖控制器中。其中的一些屬于視圖控制器,但是其中很多叫做‘展示邏輯(presentation logic)’。在MVVM術語中,類似把模型值轉(zhuǎn)換為視圖可展示的東西的邏輯和把NSDate轉(zhuǎn)換成字符串的邏輯都視為“展示邏輯”。
在我們的示意圖中少了一些東西??梢苑胖盟姓故具壿嫷臇|西。我們把它叫做‘View Model’ - 它將處于View/Controller和Model之間。

看起來好多了!這個圖表準確的描述了什么是MVVM:一個增強版本的MVC,我們正式連接了視圖和控制器,并將展示邏輯從控制器轉(zhuǎn)移到新的對象——View Model對象中。MVVM聽起來很復雜,但它本質(zhì)上是一個你已經(jīng)熟悉的MVC架構(gòu)的裝飾版本。
目前我們知道了什么是MVVM,為什么要用它呢?總之,對我而言,MVVM在iOS上動機是減少視圖控制器的復雜性,并且使展示邏輯更容易測試。我們用一些實例來看看它是怎樣完成這些目標的。
我希望你能從這篇文章中學習到非常重要的三點:
- MVVM兼容你現(xiàn)有的MVC架構(gòu)
- MVVM使應用更具可測性
- MVVM和綁定機制一起完美工作
如我們前面所見,MVVM基本上是MVC的裝飾版本。因此很容易理解它是怎樣和現(xiàn)有的使用MVC架構(gòu)的應用合并在一起的。一起創(chuàng)建一個Person模型以及相關的視圖控制器。
@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)在假設我們有了一個 PersonViewController ,在 viewDidLoad中,只需要根據(jù)模型屬性創(chuàng)建一些labels:
- (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];
}
通常的MVC的實現(xiàn)相當直接。現(xiàn)在來看怎樣通過Model View添加這些實現(xiàn)。
@interface PersonViewModel : NSObject
- (instancetype)initWithPerson:(Person *)person;
@property (nonatomic, readonly) Person *person;
@property (nonatomic, readonly) NSString *nameText;
@property (nonatomic, readonly) NSString *birthdateText;
@end
View Model的實現(xiàn)如下:
@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
我們已經(jīng)把 viewDidLoad 中展示邏輯移到了View Model中了。現(xiàn)在我們的viewDidLoad方法是非常輕量的:
- (void)viewDidLoad {
[super viewDidLoad];
self.nameLabel.text = self.viewModel.nameText;
self.birthdateLabel.text = self.viewModel.birthdateText;
}
正如你所見,從MVC架構(gòu)到MVVM并沒有太大的改變。相同的代碼,只是移來移去。它和MVC兼容,實現(xiàn)更輕量的視圖控制器,并更具可測性。
??!可測性?什么鬼?中所周知視圖控制器因其做了太多事情而很難測試。在MVVM中我們盡可能多的將代碼移動到view models中。測試視圖控制變得更加簡單,因為他們不再做所有的事情,view models很容易測試。一起瞧一瞧:
SpecBegin(Person)
NSString *salutation = @"Dr.";
NSString *firstName = @"first";
NSString *lastName = @"last";
NSDate *birthdate = [NSDate dateWithTimeIntervalSince1970:0];
it (@"should use the salutation available. ", ^{
Person *person = [[Person alloc] initWithSalutation:salutation firstName:firstName lastName:lastName birthdate:birthdate];
PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; expect(viewModel.nameText).to.equal(@"Dr. first last");
});
it (@"should not use an unavailable salutation. ", ^{
Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; expect(viewModel.nameText).to.equal(@"first last");
});
it (@"should use the correct date format. ", ^{
Person *person = [[Person alloc] initWithSalutation:nil firstName:firstName lastName:lastName birthdate:birthdate];
PersonViewModel *viewModel = [[PersonViewModel alloc] initWithPerson:person]; expect(viewModel.birthdateText).to.equal(@"Thursday January 1, 1970");
});
SpecEnd
如果我們沒有把這些邏輯移到View Model中,比較labels中的值,我們不等不創(chuàng)建完整視圖控制器和視圖?,F(xiàn)在我們可以隨意的更改我們的視圖層級而不用擔心破壞我們的單元測試。使用MVVM的測試益處顯而易見,即便是從這個簡單的例子來看,并且隨著展示邏輯越來越復雜,其好處就越明顯。
注意在這個簡單的示例中,模型是不可變的,所以我們能夠在初始化的時候分配View Models的屬性。對于可變模型,我們需要使用綁定機制以便View Model在model返回這些屬性變化時更新它的屬性。而且,一旦View Model上的model改變,視圖的屬性也要跟著改變。來自model的變化要通過View Model傳遞到View中。
在OS X上,可以使用Cocoa綁定,但是我們在iOS上沒有這個功能。 考慮使用KVO,它做得很好。 但是,對于簡單的綁定,它有很多樣板,特別是如果有很多屬性要綁定。 相反,我喜歡使用ReactiveCocoa,但使用MVVM沒有強制要使用ReactiveCocoa。 MVVM是一個很棒的范例,它代表自己,只是跟一個很好的綁定框架一起會更好。
我們已經(jīng)涵蓋很多了:從MVC衍生的MVVM,兩者如何兼容的示例,從可測試性查看MVVM,看到了MVVM和綁定機制一起能夠很好的工作。假如你有興趣學習更多關于MVVM的知識,你可以查看這篇博客,非常詳細的解釋了MVVP的優(yōu)點,或者這篇文章,關于我們怎樣在現(xiàn)有的工程中使用MVVM取得了很大的成功。我同時有完整的測試,基于MVVM叫做C-41的開放源。檢出它,如果有問題請讓我知道。
相關推薦:MVC vs MVVM