iOS 架構(gòu)模式--解密 MVC淌友,MVP,MVVM以及VIPER架構(gòu)

本文由CocoaChina譯者lynulzy(社區(qū)ID)翻譯

作者:Bohdan Orlov

原文:iOS Architecture Patterns

在 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ì)模式方面的選擇:

MVC

MVP

MVVM

VIPER

前三種設(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ù)如RZDataBindingSwiftBond

完全的函數(shù)響應(yīng)式編程并扇,比如像ReactiveCocoaRxSwift或者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>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末今膊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子伞剑,更是在濱河造成了極大的恐慌斑唬,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,402評(píng)論 6 499
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纸泄,死亡現(xiàn)場(chǎng)離奇詭異赖钞,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)聘裁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,377評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門雪营,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人衡便,你說我怎么就攤上這事献起⊙蠓茫” “怎么了?”我有些...
    開封第一講書人閱讀 162,483評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵谴餐,是天一觀的道長(zhǎng)姻政。 經(jīng)常有香客問我,道長(zhǎng)岂嗓,這世上最難降的妖魔是什么汁展? 我笑而不...
    開封第一講書人閱讀 58,165評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮厌殉,結(jié)果婚禮上食绿,老公的妹妹穿的比我還像新娘。我一直安慰自己公罕,他們只是感情好器紧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,176評(píng)論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著楼眷,像睡著了一般铲汪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上罐柳,一...
    開封第一講書人閱讀 51,146評(píng)論 1 297
  • 那天掌腰,我揣著相機(jī)與錄音,去河邊找鬼硝清。 笑死辅斟,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芦拿。 我是一名探鬼主播士飒,決...
    沈念sama閱讀 40,032評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼蔗崎!你這毒婦竟也來了酵幕?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,896評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤缓苛,失蹤者是張志新(化名)和其女友劉穎芳撒,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體未桥,經(jīng)...
    沈念sama閱讀 45,311評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笔刹,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,536評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了冬耿。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片舌菜。...
    茶點(diǎn)故事閱讀 39,696評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亦镶,靈堂內(nèi)的尸體忽然破棺而出日月,到底是詐尸還是另有隱情袱瓮,我是刑警寧澤,帶...
    沈念sama閱讀 35,413評(píng)論 5 343
  • 正文 年R本政府宣布爱咬,位于F島的核電站尺借,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏精拟。R本人自食惡果不足惜燎斩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,008評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望串前。 院中可真熱鬧瘫里,春花似錦实蔽、人聲如沸荡碾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)坛吁。三九已至,卻和暖如春铐尚,著一層夾襖步出監(jiān)牢的瞬間拨脉,已是汗流浹背遵蚜。 一陣腳步聲響...
    開封第一講書人閱讀 32,815評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工逻翁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人斯辰。 一個(gè)月前我還...
    沈念sama閱讀 47,698評(píng)論 2 368
  • 正文 我出身青樓爹脾,卻偏偏與公主長(zhǎng)得像帖旨,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子灵妨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,592評(píng)論 2 353

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