本文由CocoaChina譯者lynulzy(社區(qū)ID)翻譯
作者:Bohdan Orlov
在 iOS 中使用 MVC 架構(gòu)感覺很奇怪骗卜? 遷移到MVVM架構(gòu)又懷有疑慮宠页?聽說過 VIPER 又不確定是否真的值得切換?
相信你會(huì)找到以上問題的答案寇仓,如果沒找到請(qǐng)?jiān)谠u(píng)論中指出举户。
你將要整理出你在 iOS 環(huán)境下所有關(guān)于架構(gòu)模式的知識(shí)。我們將帶領(lǐng)大家簡(jiǎn)要的回顧一些流行的架構(gòu)遍烦,并且在理論和實(shí)踐上對(duì)它們進(jìn)行比較俭嘁,通過一些小的例子深化你的認(rèn)知。如果對(duì)文中提到的一些關(guān)鍵詞有興趣服猪,可以點(diǎn)擊連接去查看更詳細(xì)的內(nèi)容供填。
掌控設(shè)計(jì)模式可能會(huì)使人上癮,所以要當(dāng)心罢猪,你可能會(huì)對(duì)一些問題清晰明了近她,不再像閱讀之前那樣迷惑,比如下面這些問題:
誰(shuí)應(yīng)該來負(fù)責(zé)網(wǎng)絡(luò)請(qǐng)求膳帕?Model 還是 Controller 粘捎?
應(yīng)該怎樣向一個(gè)新的頁(yè)面的 ViewModel 傳入一個(gè) Model ?
誰(shuí)來創(chuàng)建一個(gè) VIPER 模塊,是 Router 還是 Presenter ?
為什么要關(guān)注架構(gòu)設(shè)計(jì)备闲?
因?yàn)榧偃缒悴魂P(guān)心架構(gòu)晌端,那么總有一天,需要在同一個(gè)龐大的類中調(diào)試若干復(fù)雜的事情恬砂,你會(huì)發(fā)現(xiàn)在這樣的條件下咧纠,根本不可能在這個(gè)類中快速的找到以及有效的修改任何bug.當(dāng)然,把這樣的一個(gè)類想象為一個(gè)整體是困難的泻骤,因此漆羔,有可能一些重要的細(xì)節(jié)總會(huì)在這個(gè)過程中會(huì)被忽略梧奢。如果現(xiàn)在的你正是處于這樣一個(gè)開發(fā)環(huán)境中,很有可能具體的情況就像下面這樣:
這個(gè)類是一個(gè)UIViewController的子類
數(shù)據(jù)直接在UIViewController中存儲(chǔ)
UIView類幾乎不做任何事情
Model 僅僅是一個(gè)數(shù)據(jù)結(jié)構(gòu)
單元測(cè)試覆蓋不了任何用例
以上這些情況仍舊會(huì)出現(xiàn)演痒,即使是你遵循了Apple的指導(dǎo)原則并且實(shí)現(xiàn)了其MVC(模式亲轨,所以,大可不必驚慌鸟顺。Apple所提出的MVC模式存在一些問題惦蚊,我們之后會(huì)詳述。
在此讯嫂,我們可以定義一個(gè)好的架構(gòu)應(yīng)該具備的特點(diǎn):
任務(wù)均衡分?jǐn)偨o具有清晰角色的實(shí)體
可測(cè)試性通常都來自與上一條(對(duì)于一個(gè)合適的架構(gòu)是非常容易)
易用性和低成本維護(hù)
為什么采用分布式?
采用分布式可以在我們要弄清楚一些事情的原理時(shí)保持一個(gè)均衡的負(fù)載蹦锋。如果你認(rèn)為你的開發(fā)工作越多,你的大腦越能習(xí)慣復(fù)雜的思維欧芽,其實(shí)這是對(duì)的莉掂。但是,不能忽略的一個(gè)事實(shí)是千扔,這種思維能力并不是線性增長(zhǎng)的憎妙,而且也并不能很快的到達(dá)峰值。所以曲楚,能夠戰(zhàn)勝這種復(fù)雜性的最簡(jiǎn)單的方法就是在遵循單一功能原則的前提下厘唾,將功能劃分給不同的實(shí)體。
為什么需要易測(cè)性洞渤?
其實(shí)這條要求對(duì)于哪些習(xí)慣了單元測(cè)試的人并不是一個(gè)問題阅嘶,因?yàn)樵谔砑恿诵碌奶匦曰蛘咭黾右恍╊惖膹?fù)雜性之后通常會(huì)失效属瓣。這就意味著载迄,測(cè)試可以避免開發(fā)者在運(yùn)行時(shí)才發(fā)現(xiàn)問題----當(dāng)應(yīng)用到達(dá)用戶的設(shè)備,每一次維護(hù)都需要浪費(fèi)長(zhǎng)達(dá)至少[一周](http://appreviewtimes.com)的時(shí)間才能再次分發(fā)給用戶抡蛙。
為什么需要易用性护昧?
這個(gè)問題沒有固定的答案,但值得一提的是粗截,最好的代碼是那些從未寫過的代碼惋耙。因此,代碼寫的越少熊昌,Bug就越少绽榛。這意味著希望寫更少的代碼不應(yīng)該被單純的解釋為開發(fā)者的懶惰,而且也不應(yīng)該因?yàn)槠珢鄹斆鞯慕鉀Q方案而忽視了它的維護(hù)開銷婿屹。
MV(X)系列概要
當(dāng)今我們已經(jīng)有很架構(gòu)設(shè)計(jì)模式方面的選擇:
前三種設(shè)計(jì)模式都把一個(gè)應(yīng)用中的實(shí)體分為以下三類:
Models--負(fù)責(zé)主要的數(shù)據(jù)或者操作數(shù)據(jù)的數(shù)據(jù)訪問層灭美,可以想象 Perspn 和 PersonDataProvider 類。
Views--負(fù)責(zé)展示層(GUI)昂利,對(duì)于iOS環(huán)境可以聯(lián)想一下以 UI 開頭的所有類届腐。
Controller/Presenter/ViewModel--負(fù)責(zé)協(xié)調(diào) Model 和 View铁坎,通常根據(jù)用戶在View上的動(dòng)作在Model上作出對(duì)應(yīng)的更改,同時(shí)將更改的信息返回到View上犁苏。
將實(shí)體進(jìn)行劃分給我們帶來了以下好處:
更好的理解它們之間的關(guān)系
復(fù)用(尤其是對(duì)于View和Model)
獨(dú)立的測(cè)試
讓我們開始了解MV(X)系列硬萍,之后再返回到VIPER模式。
MVC的過去
在我們探討Apple的MVC模式之前围详,我們來看下傳統(tǒng)的MVC模式朴乖。
傳統(tǒng)的MVC
在這里,View并沒有任何界限助赞,僅僅是簡(jiǎn)單的在Controller中呈現(xiàn)出Model的變化寒砖。想象一下,就像網(wǎng)頁(yè)一樣嫉拐,在點(diǎn)擊了跳轉(zhuǎn)到某個(gè)其他頁(yè)面的連接之后就會(huì)完全的重新加載頁(yè)面哩都。盡管在iOS平臺(tái)上實(shí)現(xiàn)這這種MVC模式是沒有任何難度的,但是它并不會(huì)為我們解決架構(gòu)問題帶來任何裨益婉徘。因?yàn)樗旧硪彩悄叮齻€(gè)實(shí)體間相互都有通信,而且是緊密耦合的盖呼。這很顯然會(huì)大大降低了三者的復(fù)用性儒鹿,而這正是我們不愿意看到的。鑒于此我們不再給出例子几晤。
“傳統(tǒng)的MVC架構(gòu)不適用于當(dāng)下的iOS開發(fā)”
蘋果推薦的MVC--愿景
Cocoa MVC
由于Controller是一個(gè)介于View 和 Model之間的協(xié)調(diào)器约炎,所以View和Model之間沒有任何直接的聯(lián)系。Controller是一個(gè)最小可重用單元蟹瘾,這對(duì)我們來說是一個(gè)好消息圾浅,因?yàn)槲覀兛傄乙粋€(gè)地方來寫邏輯復(fù)雜度較高的代碼,而這些代碼又不適合放在Model中憾朴。
理論上來講狸捕,這種模式看起來非常直觀,但你有沒有感到哪里有一絲詭異众雷?你甚至聽說過灸拍,有人將MVC的縮寫展開成(Massive View Controller),更有甚者砾省,為View controller減負(fù)也成為iOS開發(fā)者面臨的一個(gè)重要話題鸡岗。如果蘋果繼承并且對(duì)MVC模式有一些進(jìn)展,所有這些為什么還會(huì)發(fā)生编兄?
蘋果推薦的MVC--事實(shí)
Realistic Cocoa MVC
Cocoa的MVC模式驅(qū)使人們寫出臃腫的視圖控制器轩性,因?yàn)樗鼈兘?jīng)常被混雜到View的生命周期中,因此很難說View和ViewController是分離的翻诉。盡管仍可以將業(yè)務(wù)邏輯和數(shù)據(jù)轉(zhuǎn)換到Model炮姨,但是大多數(shù)情況下當(dāng)需要為View減負(fù)的時(shí)候我們卻無(wú)能為力了捌刮,View的最大的任務(wù)就是向Controller傳遞用戶動(dòng)作事件。ViewController不再承擔(dān)一切代理和數(shù)據(jù)源的職責(zé)舒岸,通常只負(fù)責(zé)一些分發(fā)和取消網(wǎng)絡(luò)請(qǐng)求以及一些其他的任務(wù)绅作,因此它的名字的由來...你懂的。
你可能會(huì)看見過很多次這樣的代碼:
1
2varuserCell?=?tableView.dequeueReusableCellWithIdentifier("identifier")?as?UserCell
userCell.configureWithUser(user)
這個(gè)cell,正是由View直接來調(diào)用Model蛾派,所以事實(shí)上MVC的原則已經(jīng)違背了俄认,但是這種情況是一直發(fā)生的甚至于人們不覺得這里有哪些不對(duì)。如果嚴(yán)格遵守MVC的話洪乍,你會(huì)把對(duì)cell的設(shè)置放在 Controller 中眯杏,不向View傳遞一個(gè)Model對(duì)象,這樣就會(huì)大大減少Controller的體積壳澳。
“Cocoa 的MVC被寫成Massive View Controller 是不無(wú)道理的岂贩。”
直到進(jìn)行單元測(cè)試的時(shí)候才會(huì)發(fā)現(xiàn)問題越來越明顯巷波。因?yàn)槟愕腣iewController和View是緊密耦合的萎津,對(duì)它們進(jìn)行測(cè)試就顯得很艱難--你得有足夠的創(chuàng)造性來模擬View和它們的生命周期,在以這樣的方式來寫View Controller的同時(shí)抹镊,業(yè)務(wù)邏輯的代碼也逐漸被分散到View的布局代碼中去锉屈。
我們看下一些簡(jiǎn)單的例子:
import?UIKit
struct?Person?{//?Model
let?firstName:?String
let?lastName:?String
}
class?GreetingViewController?:?UIViewController?{//?View?+?Controller
varperson:?Person!
let?showGreetingButton?=?UIButton()
let?greetingLabel?=?UILabel()
override?func?viewDidLoad()?{
super.viewDidLoad()
self.showGreetingButton.addTarget(self,?action:"didTapButton:",?forControlEvents:?.TouchUpInside)
}
func?didTapButton(button:?UIButton)?{
let?greeting?="Hello"+"?"+?self.person.firstName?+"?"+?self.person.lastName
self.greetingLabel.text?=?greeting
}
//?layout?code?goes?here
}
//?Assembling?of?MVC
let?model?=?Person(firstName:"David",?lastName:"Blaine")
let?view?=?GreetingViewController()
view.person?=?model;
“MVC可以在一個(gè)正在顯示的ViewController中實(shí)現(xiàn)”
這段代碼看起來可測(cè)試性并不強(qiáng),我們可以把和greeting相關(guān)的都放到GreetingModel中然后分開測(cè)試垮耳,但是這樣我們就無(wú)法通過直接調(diào)用在GreetingViewController中的UIView的方法(viewDidLoad和didTapButton方法)來測(cè)試頁(yè)面的展示邏輯了颈渊,因?yàn)橐坏┱{(diào)用則會(huì)使整個(gè)頁(yè)面都變化,這對(duì)單元測(cè)試來講并不是什么好消息终佛。
事實(shí)上俊嗽,在單獨(dú)一個(gè)模擬器中(比如iPhone 4S)加載并測(cè)試UIView并不能保證在其他設(shè)備中也能正常工作,因此我建議在單元測(cè)試的Target的設(shè)置下移除"Host Application"項(xiàng)查蓉,并且不要在模擬器中測(cè)試你的應(yīng)用乌询。
“View和Controller的接口并不適合單元測(cè)試榜贴⊥阊校”
以上所述,似乎Cocoa MVC 看起來是一個(gè)相當(dāng)差的架構(gòu)方案唬党。我們來重新評(píng)估一下文章開頭我們提出的MVC一系列的特征:
任務(wù)均攤--View和Model確實(shí)是分開的鹃共,但是View和Controller卻是緊密耦合的
可測(cè)試性--由于糟糕的分散性,只能對(duì)Model進(jìn)行測(cè)試
易用性--與其他幾種模式相比最小的代碼量驶拱。熟悉的人很多霜浴,因而即使對(duì)于經(jīng)驗(yàn)不那么豐富的開發(fā)者來講維護(hù)起來也較為容易。
如果你不想在架構(gòu)選擇上投入更多精力蓝纲,那么Cocoa MVC無(wú)疑是最好的方案阴孟,而且你會(huì)發(fā)現(xiàn)一些其他維護(hù)成本較高的模式對(duì)于你所開發(fā)的小的應(yīng)用是一個(gè)致命的打擊晌纫。
“就開發(fā)速度而言,Cocoa MVC是最好的架構(gòu)選擇方案永丝∏率”
MVP實(shí)現(xiàn)了Cocoa的MVC的愿景
Passive View variant of MVP
這看起來不正是蘋果所提出的MVC方案嗎?確實(shí)是的慕嚷,這種模式的名字叫做MVC哥牍,但是,這就是說蘋果的MVC實(shí)際上就是MVP了喝检?不嗅辣,并不是這樣的。如果你仔細(xì)回憶一下挠说,View是和Controller緊密耦合的澡谭,但是MVP的協(xié)調(diào)器Presenter并沒有對(duì)ViewController的生命周期做任何改變,因此View可以很容易的被模擬出來损俭。在Presenter中根本沒有和布局有關(guān)的代碼译暂,但是它卻負(fù)責(zé)更新View的數(shù)據(jù)和狀態(tài)。
“假如告訴你UIViewController就是View呢撩炊?”
就MVP而言外永,UIViewController的子類實(shí)際上就是Views并不是Presenters。這點(diǎn)區(qū)別使得這種模式的可測(cè)試性得到了極大的提高拧咳,付出的代價(jià)是開發(fā)速度的一些降低伯顶,因?yàn)楸仨氁鲆恍┦謩?dòng)的數(shù)據(jù)和事件綁定,從下例中可以看出:
import?UIKit
struct?Person?{//?Model
let?firstName:?String
let?lastName:?String
}
protocol?GreetingView:?class?{
func?setGreeting(greeting:?String)
}
protocol?GreetingViewPresenter?{
init(view:?GreetingView,?person:?Person)
func?showGreeting()
}
class?GreetingPresenter?:?GreetingViewPresenter?{
unowned?let?view:?GreetingView
let?person:?Person
required?init(view:?GreetingView,?person:?Person)?{
self.view?=?view
self.person?=?person
}
func?showGreeting()?{
let?greeting?="Hello"+"?"+?self.person.firstName?+"?"+?self.person.lastName
self.view.setGreeting(greeting)
}
}
class?GreetingViewController?:?UIViewController,?GreetingView?{
varpresenter:?GreetingViewPresenter!
let?showGreetingButton?=?UIButton()
let?greetingLabel?=?UILabel()
override?func?viewDidLoad()?{
super.viewDidLoad()
self.showGreetingButton.addTarget(self,?action:"didTapButton:",?forControlEvents:?.TouchUpInside)
}
func?didTapButton(button:?UIButton)?{
self.presenter.showGreeting()
}
func?setGreeting(greeting:?String)?{
self.greetingLabel.text?=?greeting
}
//?layout?code?goes?here
}
//?Assembling?of?MVP
let?model?=?Person(firstName:"David",?lastName:"Blaine")
let?view?=?GreetingViewController()
let?presenter?=?GreetingPresenter(view:?view,?person:?model)
view.presenter?=?presenter
關(guān)于整合問題的重要說明
MVP是第一個(gè)如何協(xié)調(diào)整合三個(gè)實(shí)際上分離的層次的架構(gòu)模式骆膝,既然我們不希望View涉及到Model祭衩,那么在顯示的View Controller(其實(shí)就是View)中處理這種協(xié)調(diào)的邏輯就是不正確的,因此我們需要在其他地方來做這些事情阅签。例如掐暮,我們可以做基于整個(gè)App范圍內(nèi)的路由服務(wù),由它來負(fù)責(zé)執(zhí)行協(xié)調(diào)任務(wù)政钟,以及View到View的展示路克。這個(gè)出現(xiàn)并且必須處理的問題不僅僅是在MVP模式中,同時(shí)也存在于以下集中方案中养交。
我們來看下MVP模式下的三個(gè)特性的分析:
任務(wù)均攤--我們將最主要的任務(wù)劃分到Presenter和Model精算,而View的功能較少(雖然上述例子中Model的任務(wù)也并不多)。
可測(cè)試性--非常好碎连,由于一個(gè)功能簡(jiǎn)單的View層灰羽,所以測(cè)試大多數(shù)業(yè)務(wù)邏輯也變得簡(jiǎn)單
易用性--在我們上邊不切實(shí)際的簡(jiǎn)單的例子中,代碼量是MVC模式的2倍,但同時(shí)MVP的概念卻非常清晰
“iOS 中的MVP意味著可測(cè)試性強(qiáng)廉嚼、代碼量大玫镐。”
MVP--綁定和信號(hào)
還有一些其他形態(tài)的MVP--監(jiān)控控制器的MVP怠噪。
這個(gè)變體包含了View和Model之間的直接綁定摘悴,但是Presenter仍然來管理來自View的動(dòng)作事件,同時(shí)也能勝任對(duì)View的更新舰绘。
Supervising Presenter variant of the MVP
但是我們之前就了解到蹂喻,模糊的職責(zé)劃分是非常糟糕的,更何況將View和Model緊密的聯(lián)系起來捂寿。這和Cocoa的桌面開發(fā)的原理有些相似口四。
和傳統(tǒng)的MVC一樣,寫這樣的例子沒有什么價(jià)值秦陋,故不再給出蔓彩。
MVVM--最新且是最偉大的MV(X)系列的一員
MVVM架構(gòu)是MV(X)系列最新的一員,因此讓我們希望它已經(jīng)考慮到MV(X)系列中之前已經(jīng)出現(xiàn)的問題驳概。
從理論層面來講MVVM看起來不錯(cuò)赤嚼,我們已經(jīng)非常熟悉View和Model,以及Meditor顺又,在MVVM中它是View Model更卒。
MVVM
它和MVP模式看起來非常像:
MVVM將ViewController視作View
在View和Model之間沒有緊密的聯(lián)系
此外,它還有像監(jiān)管版本的MVP那樣的綁定功能稚照,但這個(gè)綁定不是在View和Model之間而是在View和ViewModel之間蹂空。
那么問題來了,在iOS中ViewModel實(shí)際上代表什么果录?它基本上就是UIKit下的每個(gè)控件以及控件的狀態(tài)上枕。ViewModel調(diào)用會(huì)改變Model同時(shí)會(huì)將Model的改變更新到自身并且因?yàn)槲覀兘壎薞iew和ViewModel,第一步就是相應(yīng)的更新狀態(tài)弱恒。
綁定
我在MVP部分已經(jīng)提到這點(diǎn)了辨萍,但是該部分我們?nèi)詴?huì)繼續(xù)討論。綁定來自于OS X開發(fā)返弹,但是我們的iOS開發(fā)工具箱中并沒有锈玉。當(dāng)然我們有KVO和通知,但它們都不像綁定那么方便使用琉苇。
所以嘲玫,如果我們自己不想自己實(shí)現(xiàn),那么我們有兩種選擇:
基于KVO的綁定庫(kù)如RZDataBinding和SwiftBond
完全的函數(shù)響應(yīng)式編程并扇,比如像ReactiveCocoa、RxSwift或者PromiseKit
事實(shí)上抡诞,尤其是最近穷蛹,你聽到MVVM就會(huì)想到ReactiveCoca土陪,反之亦然。盡管通過簡(jiǎn)單的綁定來使用MVVM是可實(shí)現(xiàn)的肴熏,但是ReactiveCocoa卻能更好的發(fā)揮MVVM的特點(diǎn)鬼雀。
但是關(guān)于這個(gè)框架有一個(gè)不得不說的事實(shí):強(qiáng)大的能力來自于巨大的責(zé)任。當(dāng)你開始使用Reactive的時(shí)候有很大的可能就會(huì)把事情搞砸蛙吏。換句話來說就是源哩,如果發(fā)現(xiàn)了一些錯(cuò)誤,調(diào)試出這個(gè)bug可能會(huì)花費(fèi)大量的時(shí)間鸦做,看下函數(shù)調(diào)用棧:
Reactive Debugging
在我們簡(jiǎn)單的例子中励烦,F(xiàn)RF框架和KVO被過渡禁用,取而代之地我們直接去調(diào)用showGreeting方法更新ViewModel泼诱,以及通過greetingDidChange 回調(diào)函數(shù)使用屬性坛掠。
import UIKit
struct?Person?{//?Model
let?firstName:?String
let?lastName:?String
}
protocol?GreetingViewModelProtocol:?class?{
vargreeting:?String??{?get?}
vargreetingDidChange:?((GreetingViewModelProtocol)?->?())??{?get?set?}//?function?to?call?when?greeting?did?change
init(person:?Person)
func?showGreeting()
}
class?GreetingViewModel?:?GreetingViewModelProtocol?{
let?person:?Person
vargreeting:?String??{
didSet?{
self.greetingDidChange?(self)
}
}
vargreetingDidChange:?((GreetingViewModelProtocol)?->?())?
required?init(person:?Person)?{
self.person?=?person
}
func?showGreeting()?{
self.greeting?="Hello"+"?"+?self.person.firstName?+"?"+?self.person.lastName
}
}
class?GreetingViewController?:?UIViewController?{
varviewModel:?GreetingViewModelProtocol!?{
didSet?{
self.viewModel.greetingDidChange?=?{?[unowned?self]?viewModelin
self.greetingLabel.text?=?viewModel.greeting
}
}
}
let?showGreetingButton?=?UIButton()
let?greetingLabel?=?UILabel()
override?func?viewDidLoad()?{
super.viewDidLoad()
self.showGreetingButton.addTarget(self.viewModel,?action:"showGreeting",?forControlEvents:?.TouchUpInside)
}
//?layout?code?goes?here
}
//?Assembling?of?MVVM
let?model?=?Person(firstName:"David",?lastName:"Blaine")
let?viewModel?=?GreetingViewModel(person:?model)
let?view?=?GreetingViewController()
view.viewModel?=?viewModel
讓我們?cè)賮砜纯搓P(guān)于三個(gè)特性的評(píng)估:
任務(wù)均攤 -- 在例子中并不是很清晰,但是事實(shí)上治筒,MVVM的View要比MVP中的View承擔(dān)的責(zé)任多屉栓。因?yàn)榍罢咄ㄟ^ViewModel的設(shè)置綁定來更新狀態(tài),而后者只監(jiān)聽Presenter的事件但并不會(huì)對(duì)自己有什么更新耸袜。
可測(cè)試性 -- ViewModel不知道關(guān)于View的任何事情友多,這允許我們可以輕易的測(cè)試ViewModel。同時(shí)View也可以被測(cè)試堤框,但是由于屬于UIKit的范疇夷陋,對(duì)他們的測(cè)試通常會(huì)被忽略。
易用性 -- 在我們例子中的代碼量和MVP的差不多胰锌,但是在實(shí)際開發(fā)中骗绕,我們必須把View中的事件指向Presenter并且手動(dòng)的來更新View,如果使用綁定的話资昧,MVVM代碼量將會(huì)小的多酬土。
“MVVM很誘人,因?yàn)樗狭松鲜龇椒ǖ膬?yōu)點(diǎn)格带,并且由于在View層的綁定撤缴,它并不需要其他附加的代碼來更新View,盡管這樣叽唱,可測(cè)試性依然很強(qiáng)屈呕。”
VIPER--把LEGO建筑經(jīng)驗(yàn)遷移到iOS app的設(shè)計(jì)
VIPER是我們最后要介紹的棺亭,由于不是來自于MV(X)系列虎眨,它具備一定的趣味性。
迄今為止,劃分責(zé)任的粒度是很好的選擇嗽桩。VIPER在責(zé)任劃分層面進(jìn)行了迭代岳守,VIPER分為五個(gè)層次:
VIPER
交互器-- 包括關(guān)于數(shù)據(jù)和網(wǎng)絡(luò)請(qǐng)求的業(yè)務(wù)邏輯,例如創(chuàng)建一個(gè)實(shí)體(數(shù)據(jù))碌冶,或者從服務(wù)器中獲取一些數(shù)據(jù)湿痢。為了實(shí)現(xiàn)這些功能,需要使用服務(wù)扑庞、管理器譬重,但是他們并不被認(rèn)為是VIPER架構(gòu)內(nèi)的模塊,而是外部依賴罐氨。
展示器-- 包含UI層面的業(yè)務(wù)邏輯以及在交互器層面的方法調(diào)用臀规。
實(shí)體-- 普通的數(shù)據(jù)對(duì)象,不屬于數(shù)據(jù)訪問層次岂昭,因?yàn)閿?shù)據(jù)訪問屬于交互器的職責(zé)以现。
路由器-- 用來連接VIPER的各個(gè)模塊。
基本上约啊,VIPER模塊可以是一個(gè)屏幕或者用戶使用應(yīng)用的整個(gè)過程--想想認(rèn)證過程邑遏,可以由一屏完成或者需要幾步才能完成,你的模塊期望是多大的恰矩,這取決于你记盒。
當(dāng)我們把VIPER和MV(X)系列作比較時(shí),我們會(huì)在任務(wù)均攤性方面發(fā)現(xiàn)一些不同:
Model邏輯通過把實(shí)體作為最小的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換到交互器中外傅。
Controller/Presenter/ViewModel的UI展示方面的職責(zé)移到了Presenter中纪吮,但是并沒有數(shù)據(jù)轉(zhuǎn)換相關(guān)的操作。
VIPER是第一個(gè)通過路由器實(shí)現(xiàn)明確的地址導(dǎo)航模式萎胰。
“找到一個(gè)適合的方法來實(shí)現(xiàn)路由對(duì)于iOS應(yīng)用是一個(gè)挑戰(zhàn)碾盟,MV(X)系列避開了這個(gè)問題〖季梗”
例子中并不包含路由和模塊之間的交互冰肴,所以和MV(X)系列部分架構(gòu)一樣不再給出例子。
import?UIKit
struct?Person?{//?Entity?(usually?more?complex?e.g.?NSManagedObject)
let?firstName:?String
let?lastName:?String
}
struct?GreetingData?{//?Transport?data?structure?(not?Entity)
let?greeting:?String
let?subject:?String
}
protocol?GreetingProvider?{
func?provideGreetingData()
}
protocol?GreetingOutput:?class?{
func?receiveGreetingData(greetingData:?GreetingData)
}
class?GreetingInteractor?:?GreetingProvider?{
weakvaroutput:?GreetingOutput!
func?provideGreetingData()?{
let?person?=?Person(firstName:"David",?lastName:"Blaine")//?usually?comes?from?data?access?layer
let?subject?=?person.firstName?+"?"+?person.lastName
let?greeting?=?GreetingData(greeting:"Hello",?subject:?subject)
self.output.receiveGreetingData(greeting)
}
}
protocol?GreetingViewEventHandler?{
func?didTapShowGreetingButton()
}
protocol?GreetingView:?class?{
func?setGreeting(greeting:?String)
}
class?GreetingPresenter?:?GreetingOutput,?GreetingViewEventHandler?{
weakvarview:?GreetingView!
vargreetingProvider:?GreetingProvider!
func?didTapShowGreetingButton()?{
self.greetingProvider.provideGreetingData()
}
func?receiveGreetingData(greetingData:?GreetingData)?{
let?greeting?=?greetingData.greeting?+"?"+?greetingData.subject
self.view.setGreeting(greeting)
}
}
class?GreetingViewController?:?UIViewController,?GreetingView?{
vareventHandler:?GreetingViewEventHandler!
let?showGreetingButton?=?UIButton()
let?greetingLabel?=?UILabel()
override?func?viewDidLoad()?{
super.viewDidLoad()
self.showGreetingButton.addTarget(self,?action:"didTapButton:",?forControlEvents:?.TouchUpInside)
}
func?didTapButton(button:?UIButton)?{
self.eventHandler.didTapShowGreetingButton()
}
func?setGreeting(greeting:?String)?{
self.greetingLabel.text?=?greeting
}
//?layout?code?goes?here
}
//?Assembling?of?VIPER?module,?without?Router
let?view?=?GreetingViewController()
let?presenter?=?GreetingPresenter()
let?interactor?=?GreetingInteractor()
view.eventHandler?=?presenter
presenter.view?=?view
presenter.greetingProvider?=?interactor
interactor.output?=?presenter
讓我們?cè)賮碓u(píng)估一下特性:
任務(wù)均攤 -- 毫無(wú)疑問榔组,VIPER是任務(wù)劃分中的佼佼者熙尉。
可測(cè)試性 -- 不出意外地,更好的分布性就有更好的可測(cè)試性搓扯。
易用性 -- 最后你可能已經(jīng)猜到了維護(hù)成本方面的問題检痰。你必須為很小功能的類寫出大量的接口。
什么是LEGO
當(dāng)使用VIPER時(shí)锨推,你的感覺就像是用樂高積木來搭建一個(gè)城堡铅歼,這也是一個(gè)表明當(dāng)前存在一些問題的信號(hào)公壤。可能現(xiàn)在就應(yīng)用VIPER架構(gòu)還為時(shí)過早谭贪,考慮一些更為簡(jiǎn)單的模式可能會(huì)更好境钟。一些人會(huì)忽略這些問題锦担,大材小用俭识。假定他們篤信VIPER架構(gòu)會(huì)在未來給他們的應(yīng)用帶來一些好處,雖然現(xiàn)在維護(hù)起來確實(shí)是有些不合理洞渔。如果你也持這樣的觀點(diǎn)套媚,我為你推薦Generamba這個(gè)用來搭建VIPER架構(gòu)的工具。雖然我個(gè)人感覺磁椒,使用起來就像加農(nóng)炮的自動(dòng)瞄準(zhǔn)系統(tǒng)堤瘤,而不是簡(jiǎn)單的像投石器那樣的簡(jiǎn)單的拋擲。
總結(jié)
我們了解了集中架構(gòu)模式浆熔,希望你已經(jīng)找到了到底是什么在困擾你本辐。毫無(wú)疑問通過閱讀本篇文章,你已經(jīng)了解到其實(shí)并沒有完全的銀彈医增。所以選擇架構(gòu)是一個(gè)根據(jù)實(shí)際情況具體分析利弊的過程慎皱。
因此,在同一個(gè)應(yīng)用中包含著多種架構(gòu)叶骨。比如茫多,你開始的時(shí)候使用MVC,然后突然意識(shí)到一個(gè)頁(yè)面在MVC模式下的變得越來越難以維護(hù)忽刽,然后就切換到MVVM架構(gòu)天揖,但是僅僅針對(duì)這一個(gè)頁(yè)面。并沒有必要對(duì)哪些MVC模式下運(yùn)轉(zhuǎn)良好的頁(yè)面進(jìn)行重構(gòu)跪帝,因?yàn)槎呤强梢圆⒋娴摹?/p>