iOS 开发中的MVVM介绍(译)
本文譯自 :Introduction to MVVM? by Ash Furrow
2011年, 我在500px得到了第一份iOS相關的工作。 之前幾年我在大學的時候就做過iOS的項目,但是,這是我第一份正式的iOS工作。我以核心開發人員的身份被招來做一個設計優美的iPad app,僅過了幾個周,我們就交付了1.0 并繼續迭代,加入了更多的特性,同時,代碼也越來越多。
有些時候,我也不知道我在做什么,像大多數優秀的開發人員一樣,我了解我的設計。但是正是因為我太了解我的作品了,導致我無法客觀的判斷我的設計決策到底能帶來多少積極的影響,直到團隊又招了個人進來,我們意識到,壞了。
聽說過MVC么?有人叫做 大量的-視圖-控制器。 這就是當時我的感受。細節不多說,總而言之,如果重新來過,我會選擇另一條路。
那么,到底什么是MVVM?先不追究MVVM怎么來的。我們先看看一個典型的MVVM設計的iOS app 是什么樣的。
我們看到一個典型的MVC設計,模型負責數據,視圖展示界面,控制器協調調度前兩者。
稍作思考,即使視圖和控制器是不同的組件,但他們幾乎總是成雙入對,你中有我,我中有你。上次你見過一個視圖被不同控制器共用是什么時候了?或者反過來? ?所以,為什么不規范化他們的聯系呢?
??
這個應該能更精確的描述你正在寫的MVC代碼。 它沒辦法控制越來越重的控制器。典型的MVC設計的程序里,大量的邏輯都放在控制器里,有些邏輯的確屬于控制器,但還有很多并不,比如在MVVM里一種叫做展示邏輯的組件,它可以把模型映射成可以讓視圖直接展示的對象,也可以把NSdate 轉成NSString。我們的圖里漏掉了些東西,我們可以把所有的展示邏輯放在這里。我們把它叫做 ’’視圖模型’’。 它位于視圖和控制器之間:
? 看起來好點了!這個圖精確的描述了什么是MVVM:一個增強的MVC,我們創建一個新的對象銜接視圖和控制器,把展示邏輯從控制器抽出來放入其中,這個對象就是視圖模型。MVVM聽起來很高大上,但根本上,它還是我們熟悉的MVC的一個封裝。
現在我們知道什么是MVVM了,為什么有人想用它呢?對于我來說,動機是它能降低控制器的復雜度,然后就是展示邏輯更容易測試。我們馬上會用幾個例子展示一下到底是怎么做到的。
有三個要點我希望大家能夠知道:
。 MVVM能跟現有的MVC很好的兼容。
。 MVVM能讓你的程序更容易測試。
。 MVVM在綁定機制下優勢最大。
我們看到了,MVVM只是一個MVC的變種,所以很容易集成在已有的MVC設計中,先來看個簡單的 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?
不錯,現在假如我們有一個PresonViewCOntroller, 在ViewDidLoad,根據模型的屬性,設置幾個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,現在看看我們怎樣用加入一個視圖模型:?
@interface PersonViewModel : NSObject- (instancetype)initWithPerson:(Person *)person;@property (nonatomic, readonly) Person *person;@property (nonatomic, readonly) NSString *nameText; @property (nonatomic, readonly) NSString *birthdateText;@end?
我們的視圖模型實現如下:
@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?
不錯,我們把展示邏輯從viewDidLoad挪到我們的視圖模型里,新的viewDidLoad方法現在輕多了:
- (void)viewDidLoad {[super viewDidLoad];self.nameLabel.text = self.viewModel.nameText;self.birthdateLabel.text = self.viewModel.birthdateText; }?
所以,你也看到了,與MVC比,變化不大,代碼相同,只是挪了挪位置,與MVC兼容,但控制器確更輕了,也更容易測試了。
容易測試?怎么做到的?好吧,過重的控制器難于測試早就臭名昭著了,在MVVM里,我們試圖把盡可能多的代碼挪到視圖模型里,測試控制器就更容易了,因為它沒那么多邏輯了,并且視圖模型也很容易測試,看下面:?
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如果我們不把這些邏輯挪到視圖模型里,那我們得實例化一個控制器和對應的視圖,在視圖里比較label的值,這樣不僅不直接,而且會導致測試代碼更脆弱。現在我們可以隨便修改視圖,不用擔心破壞單元測試,即使上面這個簡單的栗子,MVVM的優勢也是不言而喻的。而且隨著邏輯越來越復雜,優勢更明顯。
注意,上面這個簡單的栗子,模型是不變的,所以我們可以在初始化的時候用視圖模型賦值,對于可變的模型,我們得是用綁定機制,這樣在模型更改值的時候視圖模型能同時修改。進一步,一旦視圖的模型變化,視圖的屬性也能跟著變。模型的改變能夠直接傳達到視圖上。
在OS X上,可以用Cocoa bindings,但是iOS還沒有這么吊的機制,這時候就想到了KVO,它也很棒。然而,即使對于一個簡單屬性的綁定,KVO也過于啰嗦了,更別說更多屬性的情況了。實際上,我喜歡用ReactiveCocoa。沒有限制說用MVVM必須用ReactiveCocoa。MVVM本身就是個很吊的設計模式,只不過跟很吊的綁定框架一塊只會更好而已。
我們已經說了很多了,從普通的MVC轉移到MVVM,看到他們兼容的不錯,更容易測試,和優秀的綁定框架發揮更大的威力。如果你對深入學習MVVM感興趣,你可以戳這,這里更詳細的解釋了MVVM的優勢。 也可以戳這--在用了MVVM以后達到人生的大和諧。我也有個基于MVVM的,覆蓋全部測試的開源的程序“C-41”. 猛戳查看,有問題告訴我
轉載于:https://www.cnblogs.com/larrylu2000/p/3851636.html
總結
以上是生活随笔為你收集整理的iOS 开发中的MVVM介绍(译)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小贴士
- 下一篇: Illegal access:this