用Model-View-ViewModel構(gòu)建iOS App
如果你已經(jīng)開(kāi)發(fā)一段時(shí)間的iOS應(yīng)用,你一定聽(tīng)說(shuō)過(guò)Model-View-Controller,即MVC。MVC是構(gòu)建iOS App的標(biāo)準(zhǔn)模式。然而,最近我已經(jīng)越來(lái)越厭倦MVC的一些缺點(diǎn)。在本文,我將重溫一下MVC是什么,詳述它的缺點(diǎn),并且告訴你一個(gè)新的方式來(lái)架構(gòu)你的 App:Model-View-ViewModel。拿出你的流行語(yǔ)bingo card(賓果卡,一種游戲卡片-譯者注),因?yàn)槲覀兗磳⑦M(jìn)行一次范式轉(zhuǎn)變。
Model-View-Controller
Model-View-Controller是一個(gè)用來(lái)組織代碼的權(quán)威范式。Apple甚至是這么說(shuō)的。在MVC下,所有的對(duì)象被歸類(lèi)為一個(gè)model,一個(gè)view,或一個(gè)controller。Model持有數(shù)據(jù),View顯示與用戶交互的界面,而View Controller調(diào)解Model和View之間的交互。
在上圖中,view將用戶交互通知給controller。view controller通過(guò)更新model來(lái)反應(yīng)狀態(tài)的改變。model(通常使用Key-Value-Observation)通知controller 來(lái)更新他們負(fù)責(zé)的view。大多數(shù)iOS應(yīng)用程序的代碼使用這種方式來(lái)組織。
模型model的對(duì)象通常非常非常的簡(jiǎn)單。很多時(shí)候,他們就是Core Data managed objects,或者避免使用Core Data,就是其他流行的數(shù)據(jù)模型層。根據(jù)Apple的文檔,model包括數(shù)據(jù)和操作數(shù)據(jù)的業(yè)務(wù)邏輯。在實(shí)踐中,model層往往非常薄,不管怎樣,model層的業(yè)務(wù)邏輯被拖入了controller。
視圖view通常是UIKit控件(component,這里根據(jù)習(xí)慣譯為控件)或者編碼定義的UIKit控件的集合。進(jìn)入.xib或者 Storyboard會(huì)發(fā)現(xiàn)一個(gè)app、Button、Label都是由這些可視化的和可交互的控件組成。你懂的。View不應(yīng)該直接引用model,并 且僅僅通過(guò)IBAction事件引用controller。業(yè)務(wù)邏輯很明顯不歸入view,視圖本身沒(méi)有任何業(yè)務(wù)。
還有控制器controller。Controller是app的“膠水代碼”:協(xié)調(diào)模型和視圖之間的所有交互。控制器負(fù)責(zé)管理他們所擁有的視 圖的視圖層次結(jié)構(gòu),還要響應(yīng)視圖的loading、appearing、disappearing等等,同時(shí)往往也會(huì)充滿我們不愿暴露的model的模型 邏輯以及不愿暴露給視圖的業(yè)務(wù)邏輯。這引出了***個(gè)關(guān)于MVC的問(wèn)題...
厚重的View Controller
由于大量的代碼被放進(jìn)view controller,導(dǎo)致他們變的相當(dāng)臃腫。在iOS中有的view controller里綿延成千上萬(wàn)行代碼的事并不是前所未見(jiàn)的。這些超重app的突出情況包括:厚重的View Controller很難維護(hù)(由于其龐大的規(guī)模);包含幾十個(gè)屬性,使他們的狀態(tài)難以管理;遵循許多協(xié)議(protocol),導(dǎo)致協(xié)議的響應(yīng)代碼和 controller的邏輯代碼混淆在一起。
厚重的view controller很難測(cè)試,不管是手動(dòng)測(cè)試或是使用單元測(cè)試,因?yàn)橛刑嗫赡艿臓顟B(tài)。將代碼分解成更小的多個(gè)模塊通常是件好事。
遺失的網(wǎng)絡(luò)邏輯
蘋(píng)果使用的MVC的定義是這么說(shuō)的:所有的對(duì)象都可以被歸類(lèi)為一個(gè)model,一個(gè)view,或是一個(gè)controller。就這些。那么把網(wǎng)絡(luò)代碼放哪里?和一個(gè)API通信的代碼應(yīng)該放在哪兒?
你可能試著把它放在model對(duì)象里,但是也會(huì)很棘手,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步,這樣如果一個(gè)網(wǎng)絡(luò)請(qǐng)求比持有它的model生命周期更長(zhǎng),事 情將變的復(fù)雜。顯然也不應(yīng)該把網(wǎng)絡(luò)代碼放在view里,因此只剩下controller了。這同樣是個(gè)壞主意,因?yàn)檫@加劇了厚重View Controller的問(wèn)題。
那么應(yīng)該放在那里呢?顯然MVC的3大組件根本沒(méi)有適合放這些代碼的地方。
較差的可測(cè)試性
MVC的另一個(gè)大問(wèn)題是,它不鼓勵(lì)開(kāi)發(fā)人員編寫(xiě)單元測(cè)試。由于view controller混合了視圖處理邏輯和業(yè)務(wù)邏輯,分離這些成分的單元測(cè)試成了一個(gè)艱巨的任務(wù)。大多數(shù)人選擇忽略這個(gè)任務(wù),那就是不做任何測(cè)試。
定義模糊的“Manage”
之前我提到了view controller可以管理試圖的層次結(jié)構(gòu);view controller有一個(gè)“view”屬性,并且可以通過(guò)IBOutlet訪問(wèn)視圖的任何子視圖。當(dāng)有很多outlet時(shí)這樣做不易于擴(kuò)展,在某種意義 上,***不要使用子視圖控制器(child view controller)來(lái)幫助管理子視圖(subview)。
要點(diǎn)在哪?驗(yàn)證用戶輸入的業(yè)務(wù)邏輯應(yīng)歸入controller還是model呢?
在這里有多個(gè)模糊的標(biāo)準(zhǔn),似乎沒(méi)有人能完全達(dá)成一致。貌似無(wú)論如何,view和對(duì)應(yīng)的controller都緊緊的耦合在一起,總之,還是會(huì)把它們當(dāng)成一個(gè)組件來(lái)對(duì)待。
Hey!現(xiàn)在有個(gè)點(diǎn)子...
Model-View-ViewModel
在理想的世界里,MVC也許工作的很好。然而,我們生活在真實(shí)的世界。既然我們已經(jīng)詳細(xì)說(shuō)明了MVC在典型場(chǎng)景中的問(wèn)題,那讓我們看一看一個(gè)可供替換的選擇:Model-View-ViewModel。
MVVM來(lái)自微軟,不過(guò)不要堅(jiān)持反對(duì)它。MVVM和MVC很像。它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件。
在MVVM里,view和view controller正式聯(lián)系在一起,我們把它們視為一個(gè)組件。視圖view仍然不能直接引用模型model,當(dāng)然controller也不能。相反,他們引用視圖模型view model。
view model是一個(gè)放置用戶輸入驗(yàn)證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請(qǐng)求和其他各種各樣的代碼的極好的地方。有一件事情不應(yīng)歸入view model,那就是任何視圖本身的引用。view model的概念同時(shí)適用于于iOS和OS X。(換句話說(shuō),不要在view model中使用 #import UIKit.h)
由于展示邏輯(presentation logic)放在了view model中(比如model的值映射到一個(gè)格式化的字符串),視圖控制器本身就會(huì)不再臃腫。當(dāng)你開(kāi)始使用MVVM的***方式是,可以先將一小部分邏輯放 入視圖模型,然后當(dāng)你逐漸習(xí)慣于使用這個(gè)范式的時(shí)候再遷移更多的邏輯到視圖模型中。
使用MVVM的iOS app是高度可測(cè)試的;因?yàn)関iew model包含了所有的展示邏輯并且不會(huì)引用view,所以它可以通過(guò)編程方式充分測(cè)試。雖然有眾多的hack技術(shù)參與到測(cè)試Core Data模型,但使用MVVM寫(xiě)的app可以進(jìn)行充分的單元測(cè)試。
以我的經(jīng)驗(yàn),使用MVVM會(huì)輕微的增加代碼量,但總體上減少了代碼的復(fù)雜性。這是一個(gè)劃算的交易。
回過(guò)頭再來(lái)看MVVM的圖示,你會(huì)注意到我使用了模糊的動(dòng)詞“notify”和“update”,而沒(méi)有詳細(xì)說(shuō)明該怎么做。你可以使用KVO,就像MVC那樣,但這很快就會(huì)變得難以管理。事實(shí)上,使用ReactiveCocoa會(huì)是更好的方式來(lái)組織各個(gè)部分。
關(guān)于怎么結(jié)合ReactiveCocoa來(lái)使用MVVM的信息,可以閱讀Colin Wheeler的excellent write-up或者看看我寫(xiě)的開(kāi)源app。你也可以閱讀我的關(guān)于ReactiveCocoa和MVVM的書(shū)。