標籤:blog http io ar os 使用 sp for strong
轉自:http://www.cnblogs.com/sunshine-anycall/p/4153357.html
本文翻譯自:http://www.objc.io/issue-13/mvvm.html。為了方便讀者並節約時間,有些不是和文章主題相關的就去掉了。如果讀者要看原文的話可以通過前面的url直接存取。作者也是做了iOS多年,從大學一直到現在n多年了。對於開發一款有B格的APP很有追求。學習了很多的東西,比如,silver bullet什麼的,設計模式什麼的。但是,面對急速膨脹的代碼量,即使才高八鬥也顯得無所適從。於是他開始思考人生。。。
MVC?還有另外一個解釋:Massive View Controller,翻譯過來就是一大堆的View Controller的意思。有的時候真的時有這種感覺,View Controller太多了。尤其在一個人晚上加班改bug的時候,感覺更明顯。於是,你會恨不得全部推倒重來算了!
從架構的角度考慮,也許MVC的一個衍生架構MVVM更加的合適。這裡就不討論MVVM的前世今生了。園子裡的各位.NET達人從很久以前就已在WPF上玩這個東西了。先看一下iOS的MVC是什麼樣的,然後一步一步的進入MVVM。iOS的MVC:
這是一個典型的MVC架構。Models代表資料,views代表的時使用者介面,view controllers在中間協調。仔細一想你會發現,雖然view和view controller是兩個完全不同的東西,但是他們卻緊密結合。什麼時候一個view可以和不同的view controller結合使用,或者一個view controller可以和不同的view結合使用。所以,這個架構其實是這樣的:
這樣就跟準確的描述了MVC架構下的代碼是怎麼寫的。但是,這還沒有反應出來iOS開發中大量增加的view controller。在典型的MVC應用中,很多的邏輯處理放在view controller中。有些確實是需要放在view controller中的。但是還有很大一部分式“展示邏輯(presentation logic)”。這是一個MVVM的術語,指的是那些把Model裡d的資料轉換成view中可以顯示的形式的過程。比如,把一個NSData轉換成有一定格式的NSString就是這麼一個過程。
上面的圖都少了一部分內容。缺少了的就是那部分“展示邏輯”。這一部分抽象出來之後就可以叫做“view model”。這一部分在view、view controller和model之間。
看起來更合理了把。這附圖準確的描述了什麼是MVVM,一個增強版的MVC。這裡,我們可以正式的把view和controller串連起來。並把展示邏輯從view controller中挪出去,形成一個新的對象:view model。MVVM聽起來複雜,其實就是一個穿了件新衣的MVC。本質上和MVC是一樣的。
現在您應該清楚什麼是MVVM了。那麼,為什麼要用這個東西呢?因為,使用這個架構編寫代碼可以減少view controller的複雜度,並且展示的邏輯也更容易測試。下面就通過代碼展示一下MVVM的這兩點好處。
有三點一定在文中強調:
- MVVM和您現有的MVC架構是相容的。
- MVVM使您的APP更容易測試。
- MVVM可以和綁定型的架構很好共存。
如前文所述,MVVM就是一個加強版的MVC。所以很容易看到這個模式如何和之前的MVC架構共存。我們先建立一個簡單的Person model和對應的view controller。
import UIKitclass Person { var salutation: String? var firstName: String? var lastName: String? var birthDate: NSDate? init(salutation: String?, firstName: String?, lastName: String?, birthDate: NSDate?){ self.salutation = salutation self.firstName = firstName self.lastName = lastName self.birthDate = birthDate }}
現在假設我們有一個view controller叫做PersonViewController,在viewDidLoad中要用model Person來設定一些Label的值:
override func viewDidLoad() { super.viewDidLoad() if self.model.salutation!.utf16Count > 0 { self.nameLabel.text = "\(self.model.salutation) \(self.model.firstName) \(self.model.lastName)" } else{ self.nameLabel.text = "\(self.model.firstName) \(self.model.lastName)" } var dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "EEEE MMMM d, yyyy" self.birthDateLabel.text = dateFormatter.stringFromDate(self.model.birthDate!) }
這些都是最常規的MVC代碼的寫法。下面看看如何寫view model。
import UIKitclass PersonViewModel { var person: Person! var nameText: String? var birthDateText: String? init(person: Person){ self.person = person if self.person?.salutation?.utf16Count > 0{ self.nameText = "\(self.person.salutation) \(self.person.firstName) \(self.person.lastName)" } else{ self.nameText = "\(self.person.firstName) \(self.person.lastName)" } var dateFormatter = NSDateFormatter() dateFormatter.dateFormat = "EEEE MMMM d, yyyy" self.birthDateText = dateFormatter.stringFromDate(self.person.birthDate!) }}
這樣,顯示邏輯已經從viewDidLoad方法中遷移了出去。現在viewDidLoad方法就顯得輕量了很多。
override func viewDidLoad() { super.viewDidLoad() self.nameLabel.text = self.viewModel.nameText self.birthDateLabel.text = self.viewModel.birthDateText }
你會看到,並不需要對MVC架構做多大的修改。幾乎只是原來的代碼稍作移動。完全和MVC相容,減少了view controller的重量,而且變得更加容易測試。
下面說說可測試。view controller的難以測試簡直是出了名的。在MVVM中,代碼很大一部分都移到了view model中。並且view model都是很容易測試的。如:
var person: Person! override func setUp() { super.setUp() var salutation = "Dr." var firstName = "first" var lastName = "last" var birthDate = NSDate(timeIntervalSince1970: 0) self.person = Person(salutation: salutation, firstName: firstName, lastName: lastName, birthDate: birthDate) } func testUserSalutation(){ var viewModel = PersonViewModel(person: self.person) XCTAssert(viewModel.nameText! == "Dr. first last" , "use salutation available \(viewModel.nameText!)") } func testNoSalutation(){ var localPerson = Person(salutation: nil, firstName: "first", lastName: "last", birthDate: NSDate(timeIntervalSince1970: 0)) var viewModel = PersonViewModel(person: localPerson) XCTAssert(viewModel.nameText! == "first last", "should not use salutation \(viewModel.nameText!)") } func testBirthDateFormat(){ var viewModel = PersonViewModel(person: self.person) XCTAssert(viewModel.birthDateText! == "Thursday January 1, 1970", "date \(viewModel.birthDateText!)") }
如果不是把代碼移到了view model中,測試的時候就不得不初始化一個view controller,當然還有view controller裡的一堆view。然後對比的時這些view(上例提到的時Label)的某些屬性的值。這樣不僅是非常的不方便、不直接,而且還會形成一種非常脆弱的測試:因為,view controller的試圖層次根本就不能有任何的變動,一旦變動測試即告失敗!或者測試根本就編譯不過。使用MVVM對於測試的益處是顯而易見的。在上例中還是比較簡單的邏輯。如果換做其他的產品環境的複雜的顯示邏輯的話,這種易於測試的好處會更加明顯。
注意到,上面使用到的例子都是比較簡單的。沒什麼需要修改的屬性。可以直接使用初始值初始化對象。對於有修改的model。我們需要用到某中綁定機制。這樣在model的某些值修改以後才能保證基於這個model的view model的值也跟著改變。更深一步,model值修改之後,view調用的view model的值也能跟著修改。也就是,一個對於model的修改,和model相關的view model和view的值都能同步的更新。
在OSX上,可以使用Cocoa bindings。但是iOS上沒有這個奢侈品。但是key-value observation完全可以勝任。只是在很多屬性的時候會增加代碼量。也可以使用ReactiveCocoa。當然這隻是一個選擇。一個不錯的綁定架構,會使MVVM好上加好。
我們已經講了很多關於MVVM的內容。從易於測試的角度講了MVVM。一個好的綁定架構會使MVVM更加好用。如果要更多的瞭解MVVM的話可以看這裡。這些文章更多的講述了MVVM的好處。或者這一篇,講述了在原有項目上使用MVVM並取得了不錯的效果。還有這裡的一個開源app,完全是基於MVVM的,讀者可以參考。
[愛上Swift]第14彈:在iOS項目中引入MVVM