當今我們已經(jīng)有了很多種架構(gòu)的選擇:MVC举塔、MVP绑警、MVVM、VIPER.
前三種設(shè)計模式都把一個應用中的實體分為以下三類:
Models--負責主要的數(shù)據(jù)或者操作數(shù)據(jù)的數(shù)據(jù)訪問層央渣,可以想象 Perspn 和 PersonDataProvider 類计盒。
Views--負責展示層(GUI),對于iOS環(huán)境可以聯(lián)想一下以 UI 開頭的所有類芽丹。
Controller/Presenter/ViewModel--負責協(xié)調(diào) Model 和 View北启,通常根據(jù)用戶在View上的動作在Model上作出對應的更改,同時將更改的信息返回到View上。
將實體進行劃分給我們帶來了以下好處:
- 更好的理解它們之間的關(guān)系;
- 復用(尤其是對于View和Model);
- 獨立的測試;
讓我們開始了解MV(X)系列咕村,之后再返回到VIPER模式场钉。
MVC
傳統(tǒng)的MVC模式
在我們探討Apple的MVC模式之前,我們來看下傳統(tǒng)的MVC模式
在這里懈涛,View并沒有任何界限逛万,僅僅是簡單的在Controller中呈現(xiàn)出Model的變化。想象一下批钠,就像網(wǎng)頁一樣宇植,在點擊了跳轉(zhuǎn)到某個其他頁面的連接之后就會完全的重新加載頁面。盡管在iOS平臺上實現(xiàn)這這種MVC模式是沒有任何難度的埋心,但是它并不會為我們解決架構(gòu)問題帶來任何裨益指郁。因為它本身也是,三個實體間相互都有通信拷呆,而且是緊密耦合的闲坎。這很顯然會大大降低了三者的復用性,而這正是我們不愿意看到的洋腮。鑒于此我們不再給出例子。
傳統(tǒng)的MVC架構(gòu)不適用于當下的iOS開發(fā)手形。
蘋果推薦的MVC--愿景
由于Controller是一個介于View 和 Model之間的協(xié)調(diào)器啥供,所以View和Model之間沒有任何直接的聯(lián)系。Controller是一個最小可重用單元库糠,這對我們來說是一個好消息伙狐,因為我們總要找一個地方來寫邏輯復雜度較高的代碼,而這些代碼又不適合放在Model中瞬欧。
理論上來講贷屎,這種模式看起來非常直觀,但你有沒有感到哪里有一絲詭異艘虎?你甚至聽說過唉侄,有人將MVC的縮寫展開成(Massive View Controller),更有甚者野建,為View controller減負也成為iOS開發(fā)者面臨的一個重要話題属划。如果蘋果繼承并且對MVC模式有一些進展,所有這些為什么還會發(fā)生候生?
蘋果推薦的MVC--事實
Cocoa的MVC模式驅(qū)使人們寫出臃腫的視圖控制器同眯,因為它們經(jīng)常被混雜到View的生命周期中,因此很難說View和ViewController是分離的唯鸭。盡管仍可以將業(yè)務邏輯和數(shù)據(jù)轉(zhuǎn)換到Model须蜗,但是大多數(shù)情況下當需要為View減負的時候我們卻無能為力了,View的最大的任務就是向Controller傳遞用戶動作事件。ViewController最終會承擔一切代理和數(shù)據(jù)源的職責明肮,還負責一些分發(fā)和取消網(wǎng)絡請求以及一些其他的任務菱农,因此它的名字的由來...你懂的。
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:NSStringFromClass([UITableViewCell class])];
cell.model = self.Model;
這個cell,正是由View直接來調(diào)用Model晤愧,所以事實上MVC的原則已經(jīng)違背了大莫,但是這種情況是一直發(fā)生的甚至于人們不覺得這里有哪些不對。如果嚴格遵守MVC的話官份,你會把對cell的設(shè)置放在 Controller 中只厘,不向View傳遞一個Model對象,這樣就會大大增加Controller的體積舅巷。
Cocoa 的MVC被寫成Massive View Controller 是不無道理的羔味。
直到進行單元測試的時候才會發(fā)現(xiàn)問題越來越明顯。因為你的ViewController和View是緊密耦合的钠右,對它們進行測試就顯得很艱難--你得有足夠的創(chuàng)造性來模擬View和它們的生命周期赋元,在以這樣的方式來寫View Controller的同時,業(yè)務邏輯的代碼也逐漸被分散到View的布局代碼中去飒房。
我們看下一些簡單的例子:
MVC可以在一個正在顯示的ViewController中實現(xiàn)
這段代碼看起來可測試性并不強搁凸,我們可以把和greeting相關(guān)的都放到GreetingModel中然后分開測試,但是這樣我們就無法通過直接調(diào)用在GreetingViewController中的UIView的方法(viewDidLoad和didTapButton方法)來測試頁面的展示邏輯了狠毯,因為一旦調(diào)用則會使整個頁面都變化护糖,這對單元測試來講并不是什么好消息。
事實上嚼松,在單獨一個模擬器中(比如iPhone 4S)加載并測試UIView并不能保證在其他設(shè)備中也能正常工作没咙,因此我建議在單元測試的Target的設(shè)置下移除"Host Application"項乓梨,并且不要在模擬器中測試你的應用痕寓。
View和Controller的接口并不適合單元測試偎窘。
以上所述,似乎Cocoa MVC 看起來是一個相當差的架構(gòu)方案罕偎。我們來重新評估一下MVC一系列的特征:
- 任務均攤--View和Model確實是分開的很澄,但是View和Controller卻是緊密耦合的;
- 可測試性--由于糟糕的分散性,只能對Model進行測試;
- 易用性--與其他幾種模式相比最小的代碼量颜及。熟悉的人很多痴怨,因而即使對于經(jīng)驗不那么豐富的開發(fā)者來講維護起來也較為容易;
如果你不想在架構(gòu)選擇上投入更多精力,那么Cocoa MVC無疑是最好的方案器予,而且你會發(fā)現(xiàn)一些其他維護成本較高的模式對于你所開發(fā)的小的應用是一個致命的打擊浪藻。
就開發(fā)速度而言,Cocoa MVC是最好的架構(gòu)選擇方案乾翔。
MVP
MVP實現(xiàn)了Cocoa的MVC的愿景
這看起來不正是蘋果所提出的MVC方案嗎爱葵?確實是的施戴,這種模式的名字叫做MVC,但是萌丈,這就是說蘋果的MVC實際上就是MVP了赞哗?不,并不是這樣的辆雾。如果你仔細回憶一下肪笋,View是和Controller緊密耦合的,但是MVP的協(xié)調(diào)器Presenter并沒有對ViewController的生命周期做任何改變度迂,因此View可以很容易的被模擬出來藤乙。在Presenter中根本沒有和布局有關(guān)的代碼,但是它卻負責更新View的數(shù)據(jù)和狀態(tài)惭墓。
假如告訴你UIViewController就是View呢坛梁?
就MVP而言,UIViewController的子類實際上就是Views并不是Presenters腊凶。這點區(qū)別使得這種模式的可測試性得到了極大的提高划咐,付出的代價是開發(fā)速度的一些降低,因為必須要做一些手動的數(shù)據(jù)和事件綁定钧萍,從下例中可以看出
關(guān)于整合問題的重要說明
MVP是第一個如何協(xié)調(diào)整合三個實際上分離的層次的架構(gòu)模式褐缠,既然我們不希望View涉及到Model,那么在顯示的View Controller(其實就是View)中處理這種協(xié)調(diào)的邏輯就是不正確的风瘦,因此我們需要在其他地方來做這些事情队魏。例如,我們可以做基于整個App范圍內(nèi)的路由服務弛秋,由它來負責執(zhí)行協(xié)調(diào)任務器躏,以及View到View的展示俐载。這個出現(xiàn)并且必須處理的問題不僅僅是在MVP模式中蟹略,同時也存在于以下集中方案中。
我們來看下MVP模式下的三個特性的分析:
- 任務均攤--我們將最主要的任務劃分到Presenter和Model遏佣,而View的功能較少(雖然上述例子中Model的任務也并不多);
- 可測試性--非常好挖炬,由于一個功能簡單的View層,所以測試大多數(shù)業(yè)務邏輯也變得簡單;
- 易用性--在我們上邊不切實際的簡單的例子中状婶,代碼量是MVC模式的2倍意敛,但同時MVP的概念卻非常清晰;
iOS 中的MVP意味著可測試性強、代碼量大膛虫。
MVP--綁定和信號
還有一些其他形態(tài)的MVP--監(jiān)控控制器的MVP草姻。
這個變體包含了View和Model之間的直接綁定,但是Presenter仍然來管理來自View的動作事件稍刀,同時也能勝任對View的更新撩独。
但是我們之前就了解到敞曹,模糊的職責劃分是非常糟糕的,更何況將View和Model緊密的聯(lián)系起來综膀。這和Cocoa的桌面開發(fā)的原理有些相似澳迫。
和傳統(tǒng)的MVC一樣,寫這樣的例子沒有什么價值剧劝,故不再給出橄登。
MVVM
MVVM--最新且是最偉大的MV(X)系列的一員
MVVM架構(gòu)是MV(X)系列最新的一員,因此讓我們希望它已經(jīng)考慮到MV(X)系列中之前已經(jīng)出現(xiàn)的問題讥此。
從理論層面來講MVVM看起來不錯拢锹,我們已經(jīng)非常熟悉View和Model,以及Meditor暂论,在MVVM中它是View Model面褐。
MVVM的詳情
它和MVP模式看起來非常像:
- MVVM將ViewController視作View;
- 在View和Model之間沒有緊密的聯(lián)系;
此外,它還有像監(jiān)管版本的MVP那樣的綁定功能取胎,但這個綁定不是在View和Model之間而是在View和ViewModel之間展哭。
那么問題來了,在iOS中ViewModel實際上代表什么闻蛀?它基本上就是UIKit下的每個控件以及控件的狀態(tài)匪傍。ViewModel調(diào)用會改變Model同時會將Model的改變更新到自身并且因為我們綁定了View和ViewModel,第一步就是相應的更新狀態(tài)觉痛。
綁定
我在MVP部分已經(jīng)提到這點了役衡,但是該部分我們?nèi)詴^續(xù)討論。
如果我們自己不想自己實現(xiàn)薪棒,那么我們有兩種選擇:
- 基于KVO的綁定庫如RZDataBinding和SwiftBond
- 完全的函數(shù)響應式編程手蝎,比如像ReactiveCocoa、RxSwift或者PromiseKit
事實上俐芯,尤其是最近棵介,你聽到MVVM就會想到ReactiveCoca,反之亦然吧史。盡管通過簡單的綁定來使用MVVM是可實現(xiàn)的邮辽,但是ReactiveCocoa卻能更好的發(fā)揮MVVM的特點。
但是關(guān)于這個框架有一個不得不說的事實:強大的能力來自于巨大的責任贸营。當你開始使用Reactive的時候有很大的可能就會把事情搞砸吨述。換句話來說就是,如果發(fā)現(xiàn)了一些錯誤钞脂,調(diào)試出這個bug可能會花費大量的時間揣云,看下函數(shù)調(diào)用棧:
Reactive Debugging
在我們簡單的例子中,F(xiàn)RF框架和KVO被過渡禁用冰啃,取而代之地我們直接去調(diào)用showGreeting方法更新ViewModel邓夕,以及通過greetingDidChange 回調(diào)函數(shù)使用屬性肋层。
讓我們再來看看關(guān)于三個特性的評估:
- 任務均攤 -- 在例子中并不是很清晰,但是事實上翎迁,MVVM的View要比MVP中的View承擔的責任多栋猖。因為前者通過ViewModel的設(shè)置綁定來更新狀態(tài),而后者只監(jiān)聽Presenter的事件但并不會對自己有什么更新;
- 可測試性 -- ViewModel不知道關(guān)于View的任何事情汪榔,這允許我們可以輕易的測試ViewModel蒲拉。同時View也可以被測試,但是由于屬于UIKit的范疇痴腌,對他們的測試通常會被忽略;
- 易用性 -- 在我們例子中的代碼量和MVP的差不多雌团,但是在實際開發(fā)中,我們必須把View中的事件指向Presenter并且手動的來更新View士聪,如果使用綁定的話锦援,MVVM代碼量將會小的多;
MVVM很誘人,因為它集合了上述方法的優(yōu)點剥悟,并且由于在View層的綁定灵寺,它并不需要其他附加的代碼來更新View,盡管這樣区岗,可測試性依然很強略板。
VIPER
VIPER--把LEGO建筑經(jīng)驗遷移到iOS app的設(shè)計
VIPER是我們最后要介紹的,由于不是來自于MV(X)系列慈缔,它具備一定的趣味性叮称。
迄今為止,劃分責任的粒度是很好的選擇藐鹤。VIPER在責任劃分層面進行了迭代瓤檐,VIPER分為五個層次:
- 交互器-- 包括關(guān)于數(shù)據(jù)和網(wǎng)絡請求的業(yè)務邏輯,例如創(chuàng)建一個實體(數(shù)據(jù))娱节,或者從服務器中獲取一些數(shù)據(jù)挠蛉。為了實現(xiàn)這些功能,需要使用服務括堤、管理器碌秸,但是他們并不被認為是VIPER架構(gòu)內(nèi)的模塊绍移,而是外部依賴;
- 展示器-- 包含UI層面的業(yè)務邏輯以及在交互器層面的方法調(diào)用;
- 實體-- 普通的數(shù)據(jù)對象悄窃,不屬于數(shù)據(jù)訪問層次,因為數(shù)據(jù)訪問屬于交互器的職責蹂窖。路由器-- 用來連接VIPER的各個模塊;
基本上轧抗,VIPER模塊可以是一個屏幕或者用戶使用應用的整個過程--想想認證過程,可以由一屏完成或者需要幾步才能完成瞬测,你的模塊期望是多大的横媚,這取決于你纠炮。
-當我們把VIPER和MV(X)系列作比較時,我們會在任務均攤性方面發(fā)現(xiàn)一些不同:
- Model邏輯通過把實體作為最小的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換到交互器中;
- Controller/Presenter/ViewModel的UI展示方面的職責移到了Presenter中灯蝴,但是并沒有數(shù)據(jù)轉(zhuǎn)換相關(guān)的操作;
VIPER是第一個通過路由器實現(xiàn)明確的地址導航模式恢口。
找到一個適合的方法來實現(xiàn)路由對于iOS應用是一個挑戰(zhàn),MV(X)系列避開了這個問題穷躁。
例子中并不包含路由和模塊之間的交互耕肩,所以和MV(X)系列部分架構(gòu)一樣不再給出例子。
讓我們再來評估一下特性:
- 任務均攤 -- 毫無疑問问潭,VIPER是任務劃分中的佼佼者;
- 可測試性 -- 不出意外地猿诸,更好的分布性就有更好的可測試性;
- 易用性 -- 最后你可能已經(jīng)猜到了維護成本方面的問題。你必須為很小功能的類寫出大量的接口;
想了解更多iOS學習知識請聯(lián)系:QQ(814299221)