最近準(zhǔn)備開(kāi)啟新的一個(gè)項(xiàng)目,想嘗試使用另一種架構(gòu)换帜,因?yàn)樯蟼€(gè)電商項(xiàng)目,用的是MVC架構(gòu)鹤啡,發(fā)現(xiàn)自己的一些代碼寫的有些臃腫惯驼,特別是購(gòu)物車界面有很多的業(yè)務(wù)邏輯,代碼很多都集合在了controller递瑰,本著讓代碼清晰跳座,耦合度低端铛,便于測(cè)試的原則,對(duì)新的架構(gòu)模式mvvm在網(wǎng)上進(jìn)行了相關(guān)博客和源代碼的研究疲眷。
一.MVC的弊端
MVC的利弊大家想必是有目共睹的禾蚕,Massive View Controller的說(shuō)法也并非空穴來(lái)風(fēng)的。讓我們一起探討MVC的弊端狂丝,剖析問(wèn)題產(chǎn)生原因换淆,打造一個(gè)輕量級(jí)的ViewController,明確MVC設(shè)計(jì)模式中各個(gè)角色的職責(zé)几颜。
厚重的View Controller
M:模型model的對(duì)象通常非常的簡(jiǎn)單倍试。根據(jù)Apple的文檔,model應(yīng)包括數(shù)據(jù)和操作數(shù)據(jù)的業(yè)務(wù)邏輯蛋哭。而在實(shí)踐中县习,model層往往非常薄,不管怎樣谆趾,model層的業(yè)務(wù)邏輯不應(yīng)被拖入到controller躁愿。
V:視圖view通常是UIKit控件或者編碼定義的UIKit控件的集合。View的如何構(gòu)建何必讓Controller知曉沪蓬,同時(shí)View不應(yīng)該直接引用model彤钟。業(yè)務(wù)邏輯很明顯不歸入view,視圖本身沒(méi)有任何業(yè)務(wù)跷叉。
C:控制器controller迎捺。Controller是app的中樞機(jī)構(gòu):協(xié)調(diào)模型和視圖之間的所有交互绰沥∶胧幔控制器負(fù)責(zé)管理他們所擁有的視圖的視圖層次結(jié)構(gòu)哺哼,還要響應(yīng)視圖的loading、appearing园欣、disappearing等等辫樱,同時(shí)往往也會(huì)充滿我們不愿暴露的model的模型邏輯以及不愿暴露給視圖的業(yè)務(wù)邏輯。
網(wǎng)絡(luò)數(shù)據(jù)的請(qǐng)求及后續(xù)處理俊庇,本地?cái)?shù)據(jù)庫(kù)操作狮暑,以及一些帶有工具性質(zhì)輔助方法都加大了Massive View Controller的產(chǎn)生。
遺失(無(wú)處安放)的網(wǎng)絡(luò)邏輯
蘋果使用的MVC的定義是這么說(shuō)的:所有的對(duì)象都可以被歸類為一個(gè)model辉饱,一個(gè)view搬男,或是一個(gè)controller。
你可能試著把它放在Model對(duì)象里彭沼,但是也會(huì)很棘手缔逛,因?yàn)榫W(wǎng)絡(luò)調(diào)用應(yīng)該使用異步,這樣如果一個(gè)網(wǎng)絡(luò)請(qǐng)求比持有它的model生命周期更長(zhǎng),事情將變的復(fù)雜褐奴。顯然View里面做網(wǎng)絡(luò)請(qǐng)求那就更格格不入了按脚,因此只剩下Controller了。若這樣敦冬,這又加劇了Massive View Controller的問(wèn)題辅搬。若不這樣,何處才是網(wǎng)絡(luò)邏輯的家呢脖旱?
較差的可測(cè)試性
由于View Controller混合了視圖處理邏輯和業(yè)務(wù)邏輯堪遂,分離這些成分的單元測(cè)試成了一個(gè)艱巨的任務(wù)。若一個(gè)Massive View Controller有上萬(wàn)行代碼萌庆,要你編寫個(gè)單元測(cè)試能不把你累死也會(huì)氣死.
二溶褪、MVVM(Model View View-Mode)
一種可以很好地解決Massive View Controller問(wèn)題的辦法就是將?Controller?中的展示邏輯抽取出來(lái),放置到一個(gè)專門的地方践险,而這個(gè)地方就是?viewModel?猿妈。MVVM衍生于MVC,是對(duì)?MVC?的一種演進(jìn)巍虫,它促進(jìn)了 UI 代碼與業(yè)務(wù)邏輯的分離彭则。它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件垫言。
他們之間的結(jié)構(gòu)關(guān)系如下:
MVVM 的基本概念
在MVVM?中,view?和?view controller正式聯(lián)系在一起倾剿,我們把它們視為一個(gè)組件
view?和?view controller?都不能直接引用model筷频,而是引用視圖模型(viewModel)
viewModel?是一個(gè)放置用戶輸入驗(yàn)證邏輯,視圖顯示邏輯前痘,發(fā)起網(wǎng)絡(luò)請(qǐng)求和其他代碼的地方
使用MVVM會(huì)輕微的增加代碼量凛捏,但總體上減少了代碼的復(fù)雜性
MVVM 的注意事項(xiàng):
view?引用viewModel?,但反過(guò)來(lái)不行(即不要在viewModel中引入#import UIKit.h芹缔,任何視圖本身的引用都不應(yīng)該放在viewModel中)注意:基本要求坯癣,必須滿足)
viewModel?引用model,但反過(guò)來(lái)不行
MVVM 的使用建議:
MVVM?可以兼容你當(dāng)下使用的MVC架構(gòu)最欠。
MVVM?增加你的應(yīng)用的可測(cè)試性示罗。
MVVM?配合一個(gè)綁定機(jī)制效果最好(PS:ReactiveCocoa你值得擁有)。
viewController?盡量不涉及業(yè)務(wù)邏輯芝硬,讓?viewModel?去做這些事情蚜点。
viewController?只是一個(gè)中間人,接收?view?的事件拌阴、調(diào)用?viewModel?的方法绍绘、響應(yīng)?viewModel?的變化。
viewModel?絕對(duì)不能包含視圖?view(UIKit.h),不然就跟?view?產(chǎn)生了耦合陪拘,不方便復(fù)用和測(cè)試厂镇。
viewModel之間可以有依賴。
viewModel避免過(guò)于臃腫左刽,否則重蹈Controller的覆轍捺信,變得難以維護(hù)。
MVVM 的優(yōu)勢(shì)
低耦合:View?可以獨(dú)立于Model變化和修改悠反,一個(gè)?viewModel?可以綁定到不同的?View?上
可重用性:可以把一些視圖邏輯放在一個(gè)?viewModel里面残黑,讓很多?view?重用這段視圖邏輯
獨(dú)立開(kāi)發(fā):開(kāi)發(fā)人員可以專注于業(yè)務(wù)邏輯和數(shù)據(jù)的開(kāi)發(fā)?viewModel,設(shè)計(jì)人員可以專注于頁(yè)面設(shè)計(jì)
可測(cè)試:通常界面是比較難于測(cè)試的斋否,而?MVVM?模式可以針對(duì)?viewModel來(lái)進(jìn)行測(cè)試
MVVM 的弊端
數(shù)據(jù)綁定使得Bug?很難被調(diào)試梨水。你看到界面異常了,有可能是你?View?的代碼有?Bug茵臭,也可能是?Model?的代碼有問(wèn)題疫诽。數(shù)據(jù)綁定使得一個(gè)位置的?Bug?被快速傳遞到別的位置,要定位原始出問(wèn)題的地方就變得不那么容易了旦委。
對(duì)于過(guò)大的項(xiàng)目奇徒,數(shù)據(jù)綁定和數(shù)據(jù)轉(zhuǎn)化需要花費(fèi)更多的內(nèi)存(成本)。主要成本在于:
數(shù)組內(nèi)容的轉(zhuǎn)化成本較高:數(shù)組里面每項(xiàng)都要轉(zhuǎn)化成Item對(duì)象缨硝,如果Item對(duì)象中還有類似數(shù)組摩钙,就很頭疼。
轉(zhuǎn)化之后的數(shù)據(jù)在大部分情況是不能直接被展示的查辩,為了能夠被展示胖笛,還需要第二次轉(zhuǎn)化。
只有在API返回的數(shù)據(jù)高度標(biāo)準(zhǔn)化時(shí)宜岛,這些對(duì)象原型(Item)的可復(fù)用程度才高长踊,否則容易出現(xiàn)類型爆炸,提高維護(hù)成本萍倡。
調(diào)試時(shí)通過(guò)對(duì)象原型查看數(shù)據(jù)內(nèi)容不如直接通過(guò)NSDictionary/NSArray直觀身弊。
同一API的數(shù)據(jù)被不同View展示時(shí),難以控制數(shù)據(jù)轉(zhuǎn)化的代碼列敲,它們有可能會(huì)散落在任何需要的地方阱佛。
三、案例理解
當(dāng)然戴而,與其專注于說(shuō)明 MVVM 的來(lái)歷瘫絮,不如讓我們看一個(gè)典型的 iOS 是如何構(gòu)建的,并從那里了解 MVVM:
我們看到的是一個(gè)典型的 MVC 設(shè)置填硕。Model 呈現(xiàn)數(shù)據(jù)麦萤,View 呈現(xiàn)用戶界面鹿鳖,而 View Controller 調(diào)節(jié)它兩者之間的交互。Cool壮莹!
稍微考慮一下翅帜,雖然 View 和 View Controller 是技術(shù)上不同的組件,但它們幾乎總是手牽手在一起命满,成對(duì)的涝滴。你什么時(shí)候看到一個(gè) View 能夠與不同 View Controller 配對(duì)?或者反過(guò)來(lái)胶台?所以歼疮,為什么不正規(guī)化它們的連接呢?
這更準(zhǔn)確地描述了你可能已經(jīng)編寫的 MVC 代碼诈唬。但它并沒(méi)有做太多事情來(lái)解決 iOS 應(yīng)用中日益增長(zhǎng)的重量級(jí)視圖控制器的問(wèn)題韩脏。在典型的 MVC 應(yīng)用里,許多邏輯被放在 View Controller 里铸磅。它們中的一些確實(shí)屬于 View Controller赡矢,但更多的是所謂的“表示邏輯(presentation logic)”,以 MVVM 屬術(shù)語(yǔ)來(lái)說(shuō)阅仔,就是那些將 Model 數(shù)據(jù)轉(zhuǎn)換為 View 可以呈現(xiàn)的東西的事情吹散,例如將一個(gè)?NSDate?轉(zhuǎn)換為一個(gè)格式化過(guò)的?NSString。
我們的圖解里缺少某些東西八酒,那些使我們可以把所有表示邏輯放進(jìn)去的東西空民。我們打算將其稱為 “View Model” —— 它位于 View/Controller 與 Model 之間:
看起好多了!這個(gè)圖解準(zhǔn)確地描述了什么是 MVVM:一個(gè) MVC 的增強(qiáng)版羞迷,我們正式連接了視圖和控制器界轩,并將表示邏輯從 Controller 移出放到一個(gè)新的對(duì)象里,即 View Model闭树。MVVM 聽(tīng)起來(lái)很復(fù)雜耸棒,但它本質(zhì)上就是一個(gè)精心優(yōu)化的 MVC 架構(gòu)荒澡,而 MVC 你早已熟悉报辱。
現(xiàn)在我們知道了什么是 MVVM,但為什么我們會(huì)想要去使用它呢单山?在 iOS 上使用 MVVM 的動(dòng)機(jī)碍现,對(duì)我來(lái)說(shuō),無(wú)論如何米奸,就是它能減少 View Controller 的復(fù)雜性并使得表示邏輯更易于測(cè)試昼接。通過(guò)一些例子,我們將看到它如何達(dá)到這些目標(biāo)悴晰。
此處有三個(gè)重點(diǎn)是我希望你看完本文能帶走的:
MVVM 可以兼容你當(dāng)下使用的 MVC 架構(gòu)慢睡。
MVVM 增加你的應(yīng)用的可測(cè)試性逐工。
MVVM 配合一個(gè)綁定機(jī)制效果最好。
如我們之前所見(jiàn)漂辐,MVVM 基本上就是 MVC 的改進(jìn)版泪喊,所以很容易就能看到它如何被整合到現(xiàn)有使用典型 MVC 架構(gòu)的應(yīng)用中。讓我們看一個(gè)簡(jiǎn)單的?Person?Model 以及相應(yīng)的 View Controller:
Cool髓涯!現(xiàn)在我們假設(shè)我們有一個(gè)?PersonViewController?袒啼,在?viewDidLoad?里,只需要基于它的?model?屬性設(shè)置一些 Label 即可纬纪。
這全都直截了當(dāng)蚓再,標(biāo)準(zhǔn)的 MVC。現(xiàn)在來(lái)看看我們?nèi)绾斡靡粋€(gè) View Model 來(lái)增強(qiáng)它包各。
我們的 View Model 的實(shí)現(xiàn)大概如下:
Cool摘仅!我們已經(jīng)將?viewDidLoad?中的表示邏輯放入我們的 View Model 里了。此時(shí)髓棋,我們新的?viewDidLoad就會(huì)非常輕量:
所以实檀,如你所見(jiàn),并沒(méi)有對(duì)我們的 MVC 架構(gòu)做太多改變按声。還是同樣的代碼膳犹,只不過(guò)移動(dòng)了位置。它與 MVC 兼容签则,帶來(lái)更輕量的 View Controllers须床。
可測(cè)試,嗯渐裂?是怎樣豺旬?好吧,View Controller 是出了名的難以測(cè)試柒凉,因?yàn)樗鼈冏隽颂嗍虑樽逶摹T?MVVM 里,我們?cè)囍M可能多的將代碼移入 View Model 里膝捞。測(cè)試 View Controller 就變得容易多了坦刀,因?yàn)樗鼈儾辉僮鲆淮蠖咽虑椋⑶?View Model 也非常易于測(cè)試蔬咬。讓我們來(lái)看看:
如果我們沒(méi)有將這個(gè)邏輯移入 View Model鲤遥,我們將不得不實(shí)例化一個(gè)完整的 View Controller 以及伴隨的 View,然后去比較我們 View 中 Label 的值林艘。這樣做不只是會(huì)變成一個(gè)麻煩的間接層盖奈,而且它只代表了一個(gè)十分脆弱的測(cè)試。現(xiàn)在狐援,我們可以按意愿自由地修改視圖層級(jí)而不必?fù)?dān)心破壞我們的單元測(cè)試钢坦。使用 MVVM 帶來(lái)的對(duì)于測(cè)試的好處非常清晰究孕,甚至從這個(gè)簡(jiǎn)單的例子來(lái)看也可見(jiàn)一斑,而在有更復(fù)雜的表示邏輯的情況下爹凹,這個(gè)好處會(huì)更加明顯蚊俺。
注意到在這個(gè)簡(jiǎn)單的例子中, Model 是不可變的逛万,所以我們可以只在初始化的時(shí)候指定我們 View Model 的屬性泳猬。對(duì)于可變 Model,我們還需要使用一些綁定機(jī)制宇植,這樣 View Model 就能在背后的 Model 改變時(shí)更新自身的屬性得封。此外,一旦 View Model 上的 Model 發(fā)生改變指郁,那 View 的屬性也需要更新忙上。Model 的改變應(yīng)該級(jí)聯(lián)向下通過(guò) View Model 進(jìn)入 View。
四闲坎、最后
MVVM是MVC的升級(jí)版疫粥,完全兼容當(dāng)前的MVC架構(gòu),MVVM雖然促進(jìn)了UI 代碼與業(yè)務(wù)邏輯的分離腰懂,一定程度上減輕了ViewController的臃腫度梗逮,但是View和ViewModel之間的數(shù)據(jù)綁定使得?MVVM變得復(fù)雜和難用了,如果我們不能更好的駕馭兩者之間的數(shù)據(jù)綁定绣溜,同樣會(huì)造成Controller?代碼過(guò)于復(fù)雜慷彤,代碼邏輯不易維護(hù)的問(wèn)題。
一個(gè)輕量級(jí)的ViewController是基于MVC和MVVM模式進(jìn)行代碼職責(zé)的分離而打造的怖喻。MVC和MVVM有優(yōu)點(diǎn)也有缺點(diǎn)底哗,但缺點(diǎn)在他們所帶來(lái)的好處面前時(shí)不值一提的。他們的低耦合性锚沸,封裝性跋选,可測(cè)試性,可維護(hù)性和多人協(xié)作便利大大提高了開(kāi)法效率哗蜈。
同時(shí)前标,我們需要保持的是一個(gè)擁抱變化的心,以及理性分析的態(tài)度恬叹。在新技術(shù)的面前候生,不盲從同眯,也不守舊绽昼,一切的決策都應(yīng)該建立在認(rèn)真分析的基礎(chǔ)上,這樣才能應(yīng)對(duì)技術(shù)的變化须蜗。
參考:
通俗易懂:http://www.reibang.com/p/a0c22492a620
http://www.reibang.com/p/db8400e1d40e
https://blog.csdn.net/perfect_app/article/details/56007030
https://objccn.io/issue-13-1/
https://blog.csdn.net/u011171043/article/details/50593258
http://www.cocoachina.com/ios/20170710/19801.html