iOS設(shè)計模式和架構(gòu)(1)-MVC妻柒、MVP扛拨、MVVM、VIPER

當今我們已經(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模式

傳統(tǒng)的MVC.png

在這里懈涛,View并沒有任何界限逛万,僅僅是簡單的在Controller中呈現(xiàn)出Model的變化。想象一下批钠,就像網(wǎng)頁一樣宇植,在點擊了跳轉(zhuǎn)到某個其他頁面的連接之后就會完全的重新加載頁面。盡管在iOS平臺上實現(xiàn)這這種MVC模式是沒有任何難度的埋心,但是它并不會為我們解決架構(gòu)問題帶來任何裨益指郁。因為它本身也是,三個實體間相互都有通信拷呆,而且是緊密耦合的闲坎。這很顯然會大大降低了三者的復用性,而這正是我們不愿意看到的洋腮。鑒于此我們不再給出例子。
傳統(tǒng)的MVC架構(gòu)不適用于當下的iOS開發(fā)手形。

蘋果推薦的MVC--愿景

apple的MVC.png

由于Controller是一個介于View 和 Model之間的協(xié)調(diào)器啥供,所以View和Model之間沒有任何直接的聯(lián)系。Controller是一個最小可重用單元库糠,這對我們來說是一個好消息伙狐,因為我們總要找一個地方來寫邏輯復雜度較高的代碼,而這些代碼又不適合放在Model中瞬欧。

理論上來講贷屎,這種模式看起來非常直觀,但你有沒有感到哪里有一絲詭異艘虎?你甚至聽說過唉侄,有人將MVC的縮寫展開成(Massive View Controller),更有甚者野建,為View controller減負也成為iOS開發(fā)者面臨的一個重要話題属划。如果蘋果繼承并且對MVC模式有一些進展,所有這些為什么還會發(fā)生候生?

蘋果推薦的MVC--事實

apple事實的MVC.png

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的布局代碼中去飒房。

我們看下一些簡單的例子:

代碼的例子.png

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的愿景

MVP的結(jié)構(gòu).png

這看起來不正是蘋果所提出的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ù)和事件綁定钧萍,從下例中可以看出

代碼例子1.png
代碼例子1補充.png

關(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的更新撩独。

apple的MVC.png

但是我們之前就了解到敞曹,模糊的職責劃分是非常糟糕的,更何況將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的結(jié)構(gòu).png

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)薪棒,那么我們有兩種選擇:

事實上俐芯,尤其是最近棵介,你聽到MVVM就會想到ReactiveCoca,反之亦然吧史。盡管通過簡單的綁定來使用MVVM是可實現(xiàn)的邮辽,但是ReactiveCocoa卻能更好的發(fā)揮MVVM的特點。
但是關(guān)于這個框架有一個不得不說的事實:強大的能力來自于巨大的責任贸营。當你開始使用Reactive的時候有很大的可能就會把事情搞砸吨述。換句話來說就是,如果發(fā)現(xiàn)了一些錯誤钞脂,調(diào)試出這個bug可能會花費大量的時間揣云,看下函數(shù)調(diào)用棧:

調(diào)用棧.png

Reactive Debugging

在我們簡單的例子中,F(xiàn)RF框架和KVO被過渡禁用冰啃,取而代之地我們直接去調(diào)用showGreeting方法更新ViewModel邓夕,以及通過greetingDidChange 回調(diào)函數(shù)使用屬性肋层。

代碼例子1.png
代碼例子1-補充.png

讓我們再來看看關(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分為五個層次:

VIPER.png
  • 交互器-- 包括關(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)一樣不再給出例子。

代碼例子1.png
代碼例子1_補充.png

讓我們再來評估一下特性:

  • 任務均攤 -- 毫無疑問问潭,VIPER是任務劃分中的佼佼者;
  • 可測試性 -- 不出意外地猿诸,更好的分布性就有更好的可測試性;
  • 易用性 -- 最后你可能已經(jīng)猜到了維護成本方面的問題。你必須為很小功能的類寫出大量的接口;
                            想了解更多iOS學習知識請聯(lián)系:QQ(814299221)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末狡忙,一起剝皮案震驚了整個濱河市梳虽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌灾茁,老刑警劉巖窜觉,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異北专,居然都是意外死亡竖螃,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門逗余,熙熙樓的掌柜王于貴愁眉苦臉地迎上來特咆,“玉大人,你說我怎么就攤上這事录粱∧甯瘢” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵啥繁,是天一觀的道長菜职。 經(jīng)常有香客問我,道長旗闽,這世上最難降的妖魔是什么酬核? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮适室,結(jié)果婚禮上嫡意,老公的妹妹穿的比我還像新娘。我一直安慰自己捣辆,他們只是感情好蔬螟,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著汽畴,像睡著了一般旧巾。 火紅的嫁衣襯著肌膚如雪耸序。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天鲁猩,我揣著相機與錄音坎怪,去河邊找鬼。 笑死廓握,一個胖子當著我的面吹牛芋忿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播疾棵,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼戈钢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了是尔?” 一聲冷哼從身側(cè)響起殉了,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎拟枚,沒想到半個月后薪铜,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡恩溅,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年隔箍,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脚乡。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡蜒滩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出奶稠,到底是詐尸還是另有隱情俯艰,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布锌订,位于F島的核電站竹握,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏辆飘。R本人自食惡果不足惜啦辐,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蜈项。 院中可真熱鬧芹关,春花似錦、人聲如沸战得。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽常侦。三九已至浇冰,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間聋亡,已是汗流浹背肘习。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留坡倔,地道東北人漂佩。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像罪塔,于是被迫代替她去往敵國和親投蝉。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

推薦閱讀更多精彩內(nèi)容