轉(zhuǎn)載自:藍(lán)晨鈺的博客 序猿題庫(kù)是一個(gè)擁有數(shù)千萬(wàn)用戶的創(chuàng)業(yè)公司,,從2013年題庫(kù)項(xiàng)目起步到2015年,團(tuán)隊(duì)保持了極高的生產(chǎn)效率,,使我們的產(chǎn)品完成了五個(gè)大版本和數(shù)十個(gè)小版本的高速迭代,。在如此快速的開(kāi)發(fā)過(guò)程中,如何保證代碼的質(zhì)量,,降低后期維護(hù)的成本,,以及為項(xiàng)目越來(lái)越快的版本迭代速度提供支持,成為了我們關(guān)注的重要問(wèn)題,。這篇文章將闡明我們?cè)谠愁}庫(kù) iOS 客戶端的架構(gòu)設(shè)計(jì),。 MVCMVC,,Model-View-Controller,我們從這個(gè)古老而經(jīng)典的設(shè)計(jì)模式入手,。采用 MVC 這個(gè)架構(gòu)的最大的優(yōu)點(diǎn)在于其概念簡(jiǎn)單,,易于理解,幾乎任何一個(gè)程序員都會(huì)有所了解,,幾乎每一所計(jì)算機(jī)院校都教過(guò)相關(guān)的知識(shí),。而在 iOS 客戶端開(kāi)發(fā)中,MVC 作為官方推薦的主流架構(gòu),,不但 SDK 已經(jīng)為我們實(shí)現(xiàn)好了 UIView,、UIViewController 等相關(guān)的組件,更是有大量的文檔和范例供我們參考學(xué)習(xí),,可以說(shuō)是一種非常通用而成熟的架構(gòu)設(shè)計(jì),。 但 MVC 也有他的壞處。由于 MVC 的概念過(guò)于簡(jiǎn)單樸素,,已經(jīng)越來(lái)越難以適應(yīng)如今客戶端的需求,,大量的代碼邏輯在 MVC 中并沒(méi)有定義得很清楚究竟應(yīng)該放在什么地方,導(dǎo)致他們很容易就會(huì)堆積在 Controller 里,,成為了人們所說(shuō)的 Massive View Controller,。 MVVMMVVM,Model-View-ViewModel,,一個(gè)從 MVC 模式中進(jìn)化而來(lái)的設(shè)計(jì)模式,,最早于2005年被微軟的 WPF 和 Silverlight 的架構(gòu)師 John Gossman 提出。在 iOS 開(kāi)發(fā)中實(shí)踐 MVVM 的話,,通常會(huì)把大量原來(lái)放在 ViewController 里的視圖邏輯和數(shù)據(jù)邏輯移到 ViewModel 里,,從而有效的減輕了 ViewController 的負(fù)擔(dān)。另外通過(guò)分離出來(lái)的 ViewModel 獲得了更好的測(cè)試性,,我們可以針對(duì) ViewModel 來(lái)測(cè)試,,解決了界面元素難于測(cè)試的問(wèn)題。MVVM 通常還會(huì)和一個(gè)強(qiáng)大的綁定機(jī)制一同工作,,一旦 ViewModel 所對(duì)應(yīng)的 Model 發(fā)生變化時(shí),,ViewModel 的屬性也會(huì)發(fā)生變化,而相對(duì)應(yīng)的 View 也隨即產(chǎn)生變化,。 同樣的,,MVVM 也有他的缺點(diǎn): 一個(gè)首要的缺點(diǎn)是,MVVM 的學(xué)習(xí)成本和開(kāi)發(fā)成本都很高,。MVVM 是一個(gè)年輕的設(shè)計(jì)模式,,大多數(shù)人對(duì)他的了解都不如 MVC 熟悉,基于綁定機(jī)制來(lái)進(jìn)行編程需要一定的學(xué)習(xí)才能較好的上手。同時(shí)在 iOS 客戶端開(kāi)發(fā)中,,并沒(méi)有現(xiàn)成的綁定機(jī)制可以使用,要么使用 KVO,,要么引入類(lèi)似 ReactiveCocoa 這樣的第三方庫(kù),,使得學(xué)習(xí)成本和開(kāi)發(fā)成本進(jìn)一步提高。 另一個(gè)缺點(diǎn)是,,數(shù)據(jù)綁定使 Debug 變得更難了,。數(shù)據(jù)綁定使程序異常能快速的傳遞到其他位置,在界面上發(fā)現(xiàn)的 Bug 有可能是由 ViewModel 造成的,,也有可能是由 Model 層造成的,,傳遞鏈越長(zhǎng),對(duì) Bug 的定位就越困難,。 同時(shí)還必須指出的是,,在傳統(tǒng)的 MVVM 架構(gòu)中,ViewModel 依然承載的大量的邏輯,,包括業(yè)務(wù)邏輯,,界面邏輯,數(shù)據(jù)存儲(chǔ)和網(wǎng)絡(luò)相關(guān),,使得 ViewModel 仍然有可能變得和 MVC 中 ViewController 一樣臃腫,。 在兩種架構(gòu)中權(quán)衡而產(chǎn)生的架構(gòu)兩種架構(gòu)的優(yōu)點(diǎn)都想要,缺點(diǎn)又都想避開(kāi),,我們?cè)趦煞N架構(gòu)中權(quán)衡了他們的優(yōu)缺點(diǎn),,設(shè)計(jì)出了一個(gè)新的架構(gòu),起了一個(gè)名字叫:MVVM without Binding with DataController,,架構(gòu)圖如下: ViewModel先來(lái)看右邊視圖相關(guān)的部分,,傳統(tǒng)的 MVC 當(dāng)中 ViewController 中有大量的數(shù)據(jù)展示和樣式定制的邏輯,我們引入 MVVM 中 ViewModel 的概念,,將這部分視圖邏輯移到了 ViewModel 當(dāng)中,。在這個(gè)設(shè)計(jì)中,每一個(gè) View 都會(huì)有一個(gè)對(duì)應(yīng)的 ViewModel,,其包含了這個(gè) View 數(shù)據(jù)展示和樣式定制所需要的所有數(shù)據(jù),。同時(shí),我們不引入雙向綁定機(jī)制或者觀察機(jī)制,,而是通過(guò)傳統(tǒng)的代理回調(diào)或是通知來(lái)將 UI 事件傳遞給外界,。而 ViewController 只需要生成一個(gè) ViewModel 并把這個(gè)裝配給對(duì)應(yīng)的 View,并接受相應(yīng)的 UI 事件即可,。 這樣做有幾個(gè)好處:首先是 View 的完全解耦合,,對(duì)于 View 來(lái)說(shuō),只需要確定好相應(yīng)的 ViewModel 和 UI 事件的回調(diào)接口即可與 Model 層完全隔離,;而 ViewController 可以避免與 View 的具體表現(xiàn)打交道,,這部分職責(zé)被轉(zhuǎn)交給了 ViewModel,,有效的減輕了 ViewController 的負(fù)擔(dān);同時(shí)我們棄用了傳統(tǒng)綁定機(jī)制,,使用了傳統(tǒng)的易于理解的回調(diào)機(jī)制來(lái)傳遞 UI 事件,,降低了學(xué)習(xí)成本,同時(shí)使得數(shù)據(jù)的流入和流出變得易于觀察和控制,,降低了維護(hù)了調(diào)適的成本,。 DataController接下來(lái)我們關(guān)注 Model 和 VC 之間的關(guān)系。如之前提到,,在傳統(tǒng)的 MVVM 中,,ViewModel 接管了 ViewController 的大部分職責(zé),包括數(shù)據(jù)獲取,,處理,,加工等等,導(dǎo)致其很有可能變得臃腫,。我們將這部分邏輯抽離出來(lái),,引入一個(gè)新的部件,DataController,。 ViewController 可以向 DataController 請(qǐng)求獲取或是操作數(shù)據(jù),,也可以將一些事件傳遞給 DataController,這些事件可以是 UI 事件觸發(fā)的,。DataController 在收到這些請(qǐng)求后,,再向 Model 層獲取或是更新數(shù)據(jù),最后再將得到的數(shù)據(jù)加工成 ViewController 最終需要的數(shù)據(jù)返回,。 這樣做之后,,使得數(shù)據(jù)相關(guān)的邏輯解耦合,數(shù)據(jù)的獲取,、修改,、加工都放在 Data Controller 中處理,View Controller 不關(guān)心數(shù)據(jù)如何獲得,,如何處理,,Data Controller 也不關(guān)心界面如何展示,如何交互,。同時(shí) Data Controller 因?yàn)橥耆徒缑鏌o(wú)關(guān),,所以可以有更好的測(cè)試性和復(fù)用性。 DataController 層和 Model 層之間的界限并不是僵硬的,,但需要保證每一個(gè) ViewController 都有一個(gè)對(duì)應(yīng)的 DataController,。Data Controller 更強(qiáng)調(diào)的是其作為業(yè)務(wù)邏輯對(duì)外的接口。而在 DataController 中調(diào)用更底層的 Model 層邏輯是我們推薦的編程范式,例如數(shù)據(jù)加工層,,網(wǎng)絡(luò)層,,持久層等。 在后面的例子中,,我們會(huì)更詳細(xì)的講解 DataController 的實(shí)現(xiàn)細(xì)節(jié),。 Show me the code我們以猿題庫(kù)主頁(yè)為例,展示我們是如何使用應(yīng)用這個(gè)架構(gòu)的,。 主頁(yè)有幾個(gè)部分組成,,最上面的小猴子 Banner 頁(yè),,用于滾動(dòng)展示一些活動(dòng)信息,;中間有一個(gè)用戶名字的頁(yè)面,用于展示用戶信息和答題情況以及一些心靈雞湯,;最底下的這部分是一個(gè)課目選擇頁(yè)面,,展示了用戶開(kāi)啟的科目入口,在更多選項(xiàng)里面可以進(jìn)一步配置這些科目入口,。接下來(lái)我們會(huì)以科目頁(yè)面(SubjectView)為例展示一些細(xì)節(jié),。 ViewController我們會(huì)給每一個(gè) ViewController 都創(chuàng)建一個(gè)對(duì)應(yīng)的 DataController。
例如我們給主頁(yè)建一個(gè)類(lèi)起名叫
在
接下來(lái),,ViewController 會(huì)向 DataController 請(qǐng)求 Subject 相關(guān)的數(shù)據(jù),,并在請(qǐng)求完成后,用獲得的數(shù)據(jù)生成 ViewModel,,將其裝配給 SubjectView,,完成界面渲染,代碼如下:
數(shù)據(jù)結(jié)構(gòu) 為了更好的演示,,我們接下來(lái)要介紹一下 Subject 相關(guān)的數(shù)據(jù)結(jié)構(gòu):
DataController 如我們之前所說(shuō),,每一個(gè) ViewController 都會(huì)有一個(gè)對(duì)應(yīng)的 DataController,,這一類(lèi) DataController 的主要職責(zé)是處理這個(gè)頁(yè)面上的所有數(shù)據(jù)相關(guān)的邏輯,我們稱(chēng)其為 View Related Data Controller。
上面的這個(gè)代碼
DataController 這一層是一個(gè)靈活性很高的部件,一個(gè) DataController 可以復(fù)用更小的 DataController,,這一類(lèi)更小的 DataController 通常只會(huì)包含純粹的或是更抽象的 Model 相關(guān)的邏輯,,例如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫(kù)請(qǐng)求,,或是數(shù)據(jù)加工等,。我們稱(chēng)這一類(lèi) DataController 為 Model Related Data Controller。 Model Related Data Controller 通常會(huì)為上層提供正交的數(shù)據(jù):
在我們的 事實(shí)上,,Model Related Data Controller 可以一般性的認(rèn)為就是大家經(jīng)常在寫(xiě)的 Model 層代碼,,例如 UserAgent,UserService,,PostService 之類(lèi)的服務(wù),。之后讀者若想重構(gòu)就項(xiàng)目成這個(gè)架構(gòu),大可以不必糾結(jié)于形式,,直接在 DataController 里調(diào)用舊有代碼的邏輯即可,,如圖下面這樣的行為都是允許的: ViewModel每一個(gè) View 都會(huì)有一個(gè)對(duì)應(yīng)的 ViewModel,這個(gè) ViewModel 會(huì)包含展示這個(gè) View 所需要的所有數(shù)據(jù),。 我們會(huì)使用工廠方法來(lái)創(chuàng)建 View Model,,例如這個(gè)例子里,Subject View Model 不需要關(guān)心傳遞給他是什么樣的 Subject,,所有的課目或者只是用戶開(kāi)啟的科目,。
ViewModel 可以包含更小的 ViewModel,就像 View 可以有 SubView 一樣,。SubjectView 的內(nèi)部是由一個(gè) 需要額外注意的是,,ViewModel 一般來(lái)說(shuō)會(huì)包含的顯示界面所需要的所有元素,,但粒度是可以控制,。一般來(lái)說(shuō),我們只把會(huì)因?yàn)闃I(yè)務(wù)變化而變化的部分設(shè)為 ViewModel 的一部分,,例如這里的 titleColor 和 backgroundColor 會(huì)因?yàn)橹黝}不同而變化,,但字體的大小(titleFont)卻是不會(huì)變的,,所以不需要事無(wú)巨細(xì)的都加到 ViewModel 里,。
View View 只需要定義好裝配 ViewModel 的接口和定義好 UI 回調(diào)事件即可:
渲染界面的時(shí)候,完全依靠 ViewModel 進(jìn)行,,包括 View 的 SubView 也會(huì)使用 ViewModel 里面的子 ViewModel 渲染,。
至此,我們就完成了所有的步驟,。我們回過(guò)頭再看一下 ViewController 的職責(zé)就回變的非常簡(jiǎn)單,,裝配好 View,向 DataController 請(qǐng)求數(shù)據(jù),,裝配 ViewModel,,配置給 View,,接收 View 的UI事,,一切復(fù)雜的操作都能夠的代理出去。 總結(jié)優(yōu)點(diǎn)通過(guò)上面的例子我們可以看到,,這個(gè)架構(gòu)有幾個(gè)優(yōu)點(diǎn): 層次清晰,,職責(zé)明確:和界面有關(guān)的邏輯完全劃到 ViewModel 和 View 一遍,其中 ViewModel 負(fù)責(zé)界面相關(guān)邏輯,,View 負(fù)責(zé)繪制,;Data Controller 負(fù)責(zé)頁(yè)面相關(guān)的數(shù)據(jù)邏輯,而 Model 還是負(fù)責(zé)純粹的數(shù)據(jù)層邏輯,。 ViewController 僅僅只是充當(dāng)簡(jiǎn)單的膠水作用,。 耦合度低,測(cè)試性高:除開(kāi) ViewController 外,,各個(gè)部件可以說(shuō)是完全解耦合的,,各個(gè)部分也是可以完全獨(dú)立測(cè)試的。同一個(gè)功能,,可以分別由不同的開(kāi)發(fā)人員分別進(jìn)行開(kāi)發(fā)界面和邏輯,,只需要確立好接口即可。 復(fù)用性高:解耦合帶來(lái)的額外好處就是復(fù)用性高,,例如同一個(gè)View,,只需要多一個(gè)工廠方法生成 ViewModel,就可以直接復(fù)用,。數(shù)據(jù)邏輯代碼不放在 ViewController 層也可以更方便的復(fù)用,。 學(xué)習(xí)成本低: 本質(zhì)上來(lái)說(shuō),,這個(gè)架構(gòu)屬于對(duì) MVC 的優(yōu)化,主要在于解決 Massive View Controller 問(wèn)題,,把原本屬于 View Controller 的職責(zé)根據(jù)界面和邏輯部分相應(yīng)的拆到 ViewModel 和 DataController 當(dāng)中,,所以是一個(gè)非常易于理解的架構(gòu)設(shè)計(jì),即使是新手也可以很快上手,。 開(kāi)發(fā)成本低: 完全不需要引入任何第三方庫(kù)就可以進(jìn)行開(kāi)發(fā),,也避免了因?yàn)?MVVM 維護(hù)成本高的問(wèn)題。 實(shí)施性高,,重構(gòu)成本低:可以在 MVC 架構(gòu)上逐步重構(gòu)的架構(gòu),,不需要整體重寫(xiě),是一種和 MVC 兼容的設(shè)計(jì),。 缺點(diǎn)不可否認(rèn)的是,,這個(gè)設(shè)計(jì)也有其相應(yīng)的缺點(diǎn),由于其把傳統(tǒng) MVVM 里面的 VM 拆成兩部分,,會(huì)照成下面的一些情況:
后記MVVM 是一個(gè)很棒的架構(gòu),私底下我也會(huì)用其來(lái)做一些個(gè)人項(xiàng)目,,但在公司項(xiàng)目里,,我會(huì)更慎重的考慮個(gè)中利弊。我做這個(gè)設(shè)計(jì)的時(shí)候,,心儀 MVVM 的種種好處,,又忌憚?dòng)谒姆N種壞處,再考慮到團(tuán)隊(duì)的開(kāi)發(fā)和維護(hù)成本,,所以最終設(shè)計(jì)成了如今這樣,。 個(gè)人認(rèn)為,好的架構(gòu)設(shè)計(jì)的都是和團(tuán)隊(duì)以及業(yè)務(wù)場(chǎng)景息息相關(guān)的,。我們這套架構(gòu)幫助我們解決了 ViewController 代碼堆積的問(wèn)題,,也帶來(lái)了更清晰明了的代碼層級(jí)和模塊職責(zé),同時(shí)沒(méi)有引入過(guò)多的復(fù)雜性,。希望大家也能充分理解這套架構(gòu)的適用場(chǎng)景,,在自己的 APP 架構(gòu)設(shè)計(jì)中有所借鑒,。 Lancy 2015.12.30 |
|
來(lái)自: sungkmile > 《iOS應(yīng)用架構(gòu)》