揭開MVC,MVP,MVVM和VIPER的神秘面紗
Translator-CL
Don’t miss the iOS Developer Roadmap for 2018!
在iOS中用MVC時感覺怎么樣咖熟? 有關切換到MVVM的疑問做鹰? 聽說VIPER,但不確定它是否值得泞莉?
您即將在iOS環(huán)境中構(gòu)建有關架構(gòu)模式的知識哪雕。 我們將簡要回顧一些流行的,并在理論和實踐上對它們進行比較鲫趁,僅舉幾個小例子斯嚎。
掌握設計模式可能會讓人上癮,所以要注意:現(xiàn)在挨厚,在閱讀本文之前堡僻,您可能最終會問自己更多問題,例如:
Who supposed to own networking request: a Model or a Controller?
誰應該擁有網(wǎng)絡請求:模型或控制器疫剃?
How do I pass a Model into a View Model of a new View?
如何將模型傳遞到新視圖的視圖模型苦始?
Who creates a new VIPER module: Router or Presenter?
誰創(chuàng)建了一個新的VIPER模塊:路由器或演示者?
#Why care about choosing the architecture?
因為如果你不這樣做慌申,有一天陌选,用幾十種不同的東西調(diào)試一個龐大的class理郑,你會發(fā)現(xiàn)自己無法找到并修復你class上的任何錯誤∽捎停“ 當然您炉,很難將這個class視為整個實體,因此役电,你總是會遺漏一些重要的細節(jié)赚爵。 如果您的應用程序已經(jīng)處于這種情況,則很可能是:
1法瑟、This class is the UIViewController subclass.這個類是UIViewController的子類冀膝。
2、Your data stored directly in the UIViewController您的數(shù)據(jù)直接存儲在UIViewController中
3霎挟、Your UIViews do almost nothing你的UIViews什么也沒做
4窝剖、The Model is a dumb data structure模型是一個愚蠢的數(shù)據(jù)結(jié)構(gòu)
5、Your Unit Tests cover nothing你的單元測試什么也沒有
即使您遵循Apple的指導方針并實施Apple的MVC模式酥夭,這種情況也可能發(fā)生赐纱,所以不要感到難過。 Apple的MVC有問題熬北,但我們稍后會再回過頭來看看疙描。
讓我們定義一個好架構(gòu)的功能:
1、Balanced distribution of responsibilities among entities with strict roles.
在具有嚴格作用的實體之間平衡分配責任讶隐。
2起胰、Testability usually comes from the first feature (and don’t worry: it is easy with appropriate architecture).
可測試性通常來自第一個功能(不用擔心:使用適當?shù)募軜?gòu)很容易)。
3巫延、Ease of use and a low maintenance cost.
易于使用效五,維護成本低。
#Why Distribution?
為何分配烈评?
在我們試圖弄清楚事情是如何運作的時候火俄,分配對我們的大腦負有相當大的負擔犯建。 如果你認為你的開發(fā)越多讲冠,你的大腦就越能適應理解復雜性,那么你就是對的适瓦。 但是這種能力不能線性擴展并且很快達到上限竿开。 因此,打敗復雜性的最簡單方法是按照單一責任原則(Single responsibility principle)在多個實體之間劃分責任玻熙。
為什么可測性否彩?
對于那些已經(jīng)對單元測試表示感謝的人來說,這通常不是問題嗦随,單元測試在添加新功能后失敗或者由于重構(gòu)了一些復雜的類列荔。 這意味著測試使這些開發(fā)人員免于在運行時發(fā)現(xiàn)問題敬尺,這可能發(fā)生在應用程序位于用戶設備上并且修復需要一周時間才能到達用戶時。
為什么易于使用贴浙?
這不需要答案砂吞,但值得一提的是,最好的代碼是從未編寫過的代碼崎溃。 因此蜻直,您擁有的代碼越少,您擁有的錯誤就越少袁串。 這意味著編寫更少代碼的愿望永遠不應僅僅由開發(fā)人員的懶惰來解釋概而,而且您不應該傾向于關注維護成本的智能解決方案。
MV(X)要領
現(xiàn)在囱修,在架構(gòu)設計模式方面赎瑰,我們有很多選擇:
* MVC
* MVP
* MVVM
* VIPER
前三個假設將應用程序的實體分為以下三類:
Models - 負責域數(shù)據(jù)或操作數(shù)據(jù)的數(shù)據(jù)訪問層,思考'Person'或'PersonDataProvider'類蔚袍。
Views - 負責表示層(GUI)乡范,對于iOS環(huán)境,可以思考以“UI”前綴開頭的所有內(nèi)容啤咽。
Controller / Presenter / ViewModel? - 模型和視圖之間的粘合劑或介體晋辆,通常負責通過響應用戶在View上執(zhí)行的操作以及使用模型更改更新視圖來更改模型。
實體劃分允許我們:
* 更好地理解它們(我們已經(jīng)知道)
* 重用它們(主要適用于視圖和模型)
* 獨立測試它們
讓我們從MV(X)模式開始宇整,稍后再回到VIPER瓶佳。
#MVC
以前是怎樣的
在討論Apple的MVC愿景之前,讓我們來看看傳統(tǒng)的MVC鳞青。
在這種情況下霸饲,View是無狀態(tài)的。 一旦Model改變臂拓,它就由Controller簡單地渲染厚脉。 一旦按下鏈接導航到其他地方,就可以想象網(wǎng)頁完全重新加載胶惰。 盡管可以在iOS應用程序中實現(xiàn)傳統(tǒng)的MVC傻工,但由于架構(gòu)問題,它沒有多大意義 - 所有三個實體都緊密耦合孵滞,每個實體都知道其他兩個實體中捆。 這大大降低了每個人的可重用性 - 這不是您希望在應用程序中擁有的內(nèi)容。 出于這個原因坊饶,我們甚至試圖編寫一個規(guī)范的MVC示例泄伪。
`Traditional MVC doesn't seems to be applicable to modern iOS development.`
#Apple’s MVC
Expectation
Controller是View和Model之間的中介,因此他們不了解彼此匿级。 最不可重復使用的是Controller蟋滴,這通常對我們來說很好染厅,因為我們必須有一個適合所有那些不適合模型的棘手業(yè)務邏輯的地方。
從理論上講津函,它看起來很簡單糟秘,但你覺得有些不對勁,對吧球散? 你甚至聽說過人們將MVC簡化為大規(guī)模視圖控制器尿赚。 而且,視圖控制器卸載成為iOS開發(fā)人員的一個重要主題蕉堰。 Apple剛剛采用傳統(tǒng)的MVC并對其進行了一些改進凌净,為什么會發(fā)生這種情況呢?
Reality
Cocoa MVC鼓勵您編寫Massive View控制器屋讶,因為它們參與了View的生命周期冰寻,很難說它們是分開的。 雖然您仍然可以將一些業(yè)務邏輯和數(shù)據(jù)轉(zhuǎn)換卸載到模型皿渗,但是在將工作卸載到View時沒有太多選擇斩芭,在大多數(shù)情況下,View的所有責任都是發(fā)送操作 到控制器乐疆。 視圖控制器最終成為所有事物的委托和數(shù)據(jù)源划乖,并且通常負責調(diào)度和取消網(wǎng)絡請求,并且......您可以對其進行命名挤土。
你有多少次見過這樣的代碼:
```
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)
```
這個cell琴庵,即直接使用模型配置的View,因此違反了MVC指南仰美,但這種情況一直在發(fā)生迷殿,通常人們不會覺得它是錯誤的。 如果您嚴格遵循MVC咖杂,那么您應該從控制器配置單元格庆寺,并且不要將模型傳遞到View中,這將進一步增加Controller的大小诉字。
##Cocoa MVC合理地縮寫為Massive View Controller懦尝。
在進行單元測試之前,問題可能并不明顯(希望在項目中確實如此)奏窑。 由于視圖控制器與視圖緊密耦合导披,因此在測試視圖及其生命周期時必須非常有創(chuàng)意屈扎,同時以這種方式編寫視圖控制器的代碼埃唯,使業(yè)務邏輯分離得太多 盡可能從視圖布局代碼。
我們來看看簡單的playground示例:
```
import UIKit
struct Person { // Model
? ? let firstName: String
? ? let lastName: String
}
class GreetingViewController : UIViewController { // View + Controller
? ? var person: 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;
```
##可以在呈現(xiàn)視圖控制器中執(zhí)行MVC組裝
這似乎不太可測試鹰晨,對嗎墨叛? 我們可以將問候的生成移動到新的GreetingModel類中并單獨測試它止毕,但是我們無法在GreetingViewController內(nèi)部測試任何表示邏輯(盡管上面的例子中沒有太多這樣的邏輯)而沒有直接調(diào)用UIView相關的方法( viewDidLoad,didTapButton)可能會導致加載所有視圖漠趁,這對單元測試很不利扁凛。
實際上,在一個模擬器(例如iPhone 4S)上加載和測試UIViews并不能保證它在其他設備(例如iPad)上正常工作闯传,因此我建議從單元測試目標配置中刪除“Host Application” 并在沒有應用程序在模擬器上運行的情況下運行測試谨朝。
##單元測試不能真正測試視圖和控制器之間的交互
盡管如此,Cocoa MVC似乎是一個非常糟糕的選擇模式甥绿。 但是字币,讓我們根據(jù)本文開頭定義的功能對其進行評估:
* Distribution?—?the View and the Model in fact separated, but the View and the Controller are tightly coupled.
視圖和模型實際上是分開的,但視圖和控制器是緊密耦合的共缕。
* Testability?—?due to the bad distribution you’ll probably only test your Model.由于分布不良洗出,您可能只測試您的模型。
* Ease of use?—?the least amount of code among others patterns. In addition everyone is familiar with it, thus, it’s easily maintained even by the unexperienced developers.其他模式中代碼量最少图谷。 此外翩活,每個人都熟悉它,因此便贵,即使是沒有經(jīng)驗的開發(fā)人員也能輕松維護它菠镇。
如果您還沒準備好在您的架構(gòu)上投入更多時間,那么Cocoa MVC就是您選擇的模式承璃,并且您覺得維護成本較高的東西對于您的小型寵物項目來說太過分了辟犀。
總結(jié):就開發(fā)速度而言,Cocoa MVC是最好的架構(gòu)模式绸硕。
#MVP
Cocoa MVC’s promises delivered
它看起來不像Apple的MVC嗎堂竟? 是的,確實如此玻佩,它的名字是MVP(Passive View變體)出嘹。 但是等一下......這是否意味著Apple的MVC實際上是MVP? 不咬崔,它不是税稼,因為如果你還記得那里,View與Controller緊密耦合垮斯,而MVP的中介Presenter與視圖控制器的生命周期無關郎仆,而且View可以很容易地被嘲笑,所以 Presenter中根本沒有布局代碼兜蠕,但它負責使用數(shù)據(jù)和狀態(tài)更新View扰肌。
如果我告訴你,UIViewController就是View熊杨。
就MVP而言曙旭,UIViewController子類實際上是Views而不是Presenters盗舰。 這種區(qū)別提供了極好的可測試性,這是以開發(fā)速度為代價的桂躏,因為您必須制作手動數(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 {
? ? var presenter: 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
```
##Important note regarding assembly
MVP是第一個揭示由于具有三個實際上分離的層而發(fā)生的組裝問題的模式。
由于我們不希望View了解Model剂习,因此在呈現(xiàn)視圖控制器(即View)中執(zhí)行匯編是不對的蛮位,因此我們必須在其他地方執(zhí)行。 例如鳞绕,我們可以制作應用程序范圍的Router服務土至,該服務將負責執(zhí)行程序集和View-to-View演示。 這個問題出現(xiàn)了猾昆,不僅要在MVP中解決陶因,還要在以下所有模式中解決。
讓我們來看看MVP的功能:
* Distribution?—?we have the most of responsibilities divided between the Presenter and the Model, with the pretty dumb View (in the example above the Model is dumb as well).分布 - 我們在Presenter和模型之間分配了大部分職責垂蜗,其中啞視圖(在模型上面的示例中也是愚蠢的)楷扬。
* Testability?—?is excellent, we can test most of the business logic due to the dumb View.可測試性 - 非常好,我們可以測試大多數(shù)業(yè)務邏輯贴见,因為它們是啞視圖烘苹。
* Easy of use?—?in our unrealistically simple example, the amount of code is doubled compared to the MVC, but at the same time, idea of the MVP is very clear.
易于使用 - 在我們不切實際的簡單示例中,與MVC相比片部,代碼量增加了一倍镣衡,但與此同時,MVP的概念非常清晰档悠。
總結(jié):iOS中的MVP意味著極好的可測試性和大量代碼廊鸥。
#MVP
With Bindings and Hooters
還有MVP的另一種風格 - 監(jiān)督控制器MVP。 此變體包括View和Model的直接綁定辖所,而Presenter(監(jiān)督控制器)仍然可以處理來自View的操作惰说,并且能夠更改View。
但正如我們之前已經(jīng)知道的那樣缘回,模糊的責任分離是不好的吆视,以及視圖和模型的緊密耦合。 這類似于Cocoa桌面開發(fā)中的工作方式酥宴。
與傳統(tǒng)的MVC相同啦吧,我沒有看到為有缺陷的架構(gòu)編寫示例的重點。
#MVVM
The latest and the greatest of the MV(X) kind
MVVM是最新的MV(X)類型拙寡,因此授滓,我們希望它考慮到MV(X)之前面臨的問題。
從理論上講,Model-View-ViewModel看起來非常好褒墨。 View和Model已經(jīng)為我們所熟悉,但也是Mediator擎宝,表示為View Model郁妈。
它與MVP非常相似:
* MVVM將視圖控制器視為視圖
* View和Model之間沒有緊密耦合
此外,它確實像MVP的監(jiān)督版本一樣綁定; 但是绍申,這次不在View和Model之間噩咪,而是在View和View Model之間。
那么iOS現(xiàn)實中的View Model是什么极阅? 它基本上是UIKit獨立表示您的View及其狀態(tài)胃碾。 視圖模型調(diào)用模型中的更改并使用更新的模型更新自身斑胜,并且由于我們在視圖和視圖模型之間有綁定曹体,因此第一個更新。
Bindings
我在MVP部分簡要提到過它們堵腹,但我們在這里討論它們奔脐。 綁定開箱即用于OS X開發(fā)俄周,但我們在iOS工具箱中沒有它們。 當然我們有KVO和通知髓迎,但它們不如綁定方便峦朗。
所以,如果我們不想自己編寫排龄,我們有兩個選擇:
* One of the KVO based binding libraries like the [RZDataBinding](https://github.com/Raizlabs/RZDataBinding) or the [SwiftBond](https://github.com/DeclarativeHub/Bond)
* The full scale [functional reactive programming](https://gist.github.com/JaviLorbada/4a7bd6129275ebefd5a6) beasts like [ReactiveCocoa](https://github.com/ReactiveCocoa/ReactiveCocoa), [RxSwift](https://github.com/ReactiveX/RxSwift/) or [PromiseKit](https://github.com/mxcl/PromiseKit).
事實上波势,如今,如果你聽到“MVVM” - 你認為ReactiveCocoa橄维,反之亦然尺铣。 盡管可以使用簡單綁定構(gòu)建MVVM,但ReactiveCocoa(或兄弟姐妹)將允許您獲得大部分MVVM争舞。
關于反應性框架有一個痛苦的事實:強大的力量伴隨著巨大的責任迄埃。 當你被動反應時,很容易弄亂事情兑障。 換句話說侄非,如果你做錯了什么,你可能會花很多時間調(diào)試應用程序流译,所以只需看看這個調(diào)用堆棧逞怨。
在我們的簡單示例中,F(xiàn)RF框架甚至KVO都是一種矯枉過正福澡,相反叠赦,我們將明確要求View Model使用showGreeting方法進行更新,并使用greetingDidChange回調(diào)函數(shù)的簡單屬性。
```
import UIKit
struct Person { // Model
? ? let firstName: String
? ? let lastName: String
}
protocol GreetingViewModelProtocol: class {
? ? var greeting: String? { get }
? ? var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change
? ? init(person: Person)
? ? func showGreeting()
}
class GreetingViewModel : GreetingViewModelProtocol {
? ? let person: Person
? ? var greeting: String? {
? ? ? ? didSet {
? ? ? ? ? ? self.greetingDidChange?(self)
? ? ? ? }
? ? }
? ? var greetingDidChange: ((GreetingViewModelProtocol) -> ())?
? ? required init(person: Person) {
? ? ? ? self.person = person
? ? }
? ? func showGreeting() {
? ? ? ? self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName
? ? }
}
class GreetingViewController : UIViewController {
? ? var viewModel: GreetingViewModelProtocol! {
? ? ? ? didSet {
? ? ? ? ? ? self.viewModel.greetingDidChange = { [unowned self] viewModel in
? ? ? ? ? ? ? ? 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
```
再次回到我們的功能評估:
* Distribution?—?it is not clear in our tiny example, but, in fact, the MVVM’s View has more responsibilities than the MVP’s View. Because the first one updates it’s state from the View Model by setting up bindings, when the second one just forwards all events to the Presenter and doesn’t update itself.
分布 - 在我們的小例子中并不清楚除秀,但事實上糯累,MVVM的View比MVP的View有更多的責任。 因為第一個通過設置綁定從View Model更新它的狀態(tài)册踩,而第二個只是將所有事件轉(zhuǎn)發(fā)給Presenter并且不自行更新泳姐。
* Testability?—?the View Model knows nothing about the View, this allows us to test it easily. The View might be also tested, but since it is UIKit dependant you might want to skip it.
可測試性 -? View Model對View一無所知,這使我們可以輕松地測試它暂吉。 View也可能已經(jīng)過測試胖秒,但由于它依賴于UIKit,您可能希望跳過它慕的。
* Easy of use?—?its has the same amount of code as the MVP in our example, but in the real app where you’d have to forward all events from the View to the Presenter and to update the View manually, MVVM would be much skinnier if you used bindings.
易于使用 - 它與我們示例中的MVP具有相同數(shù)量的代碼阎肝,但在真實的應用程序中,您必須將所有事件從View轉(zhuǎn)發(fā)到Presenter并手動更新View肮街,MVVM將更加出色 如果你使用綁定风题。
總結(jié):MVVM非常有吸引力,因為它結(jié)合了上述方法的優(yōu)點嫉父,此外俯邓,由于View端的綁定,它不需要額外的View更新代碼熔号。 盡管如此稽鞭,可測試性仍處于良好水平。
#VIPER
樂高的建筑體驗轉(zhuǎn)移到iOS應用程序設計中
VIPER是我們的最后一位候選人引镊,特別有趣朦蕴,因為它不是來自MV(X)類別。
到目前為止弟头,您必須同意責任的粒度非常好吩抓。 VIPER對分離職責的想法進行了另一次迭代,這次我們有五個層次赴恨。
* Interactor?—?contains business logic related to the data (Entities) or networking, like creating new instances of entities or fetching them from the server. For those purposes you’ll use some Services and Managers which are not considered as a part of VIPER module but rather an external dependency.
交互者 - 包含與數(shù)據(jù)(實體)或網(wǎng)絡相關的業(yè)務邏輯疹娶,如創(chuàng)建實體的新實例或從服務器獲取實體。 出于這些目的伦连,您將使用一些服務和管理器雨饺,這些服務和管理器不被視為VIPER模塊的一部分,而是外部依賴項惑淳。
* Presenter?—?contains the UI related (but UIKit independent) business logic, invokes methods on the Interactor.? ?
Presenter? - 包含UI相關(但UIKit獨立)業(yè)務邏輯额港,調(diào)用Interactor上的方法.
* Entities?—?your plain data objects, not the data access layer, because that is a responsibility of the Interactor.
實體 - 您的普通數(shù)據(jù)對象,而不是數(shù)據(jù)訪問層歧焦,因為這是交互者的責任移斩。
* Router?—?responsible for the segues between the VIPER modules.
路由器 - 負責VIPER模塊之間的隔離。
基本上,VIPER模塊可以是一個屏幕或應用程序的整個user story - 想一想身份驗證向瓷,可以是一個屏幕或幾個相關的屏幕肠套。 你的“樂高”積木有多小猖任? - 隨你便你稚。
如果我們將它與MV(X)類型進行比較,我們會看到責任分布的一些差異:
* Model (data interaction) logic shifted into the Interactor with the Entities as dumb data structures.
模型(數(shù)據(jù)交互)邏輯移入Interactor超升,實體作為啞數(shù)據(jù)結(jié)構(gòu)入宦。
* Only the UI representation duties of the Controller/Presenter/ViewModel moved into the Presenter, but not the data altering capabilities.
只有Controller / Presenter / ViewModel的UI表示職責移入了Presenter哺徊,而不是數(shù)據(jù)更改功能室琢。
* VIPER is the first pattern which explicitly addresses navigation responsibility, which is supposed to be resolved by the Router.
VIPER是第一個明確解決導航責任的模式,它應該由路由器解決落追。
正確的路由方式對iOS應用程序來說是一個挑戰(zhàn)盈滴,MV(X)模式根本無法解決這個問題。
該示例不包括模塊之間的路由或交互轿钠,因為MV(X)模式根本不涉及這些主題巢钓。
```
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 {
? ? weak var output: 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 {
? ? weak var view: GreetingView!
? ? var greetingProvider: GreetingProvider!
? ? func didTapShowGreetingButton() {
? ? ? ? self.greetingProvider.provideGreetingData()
? ? }
? ? func receiveGreetingData(greetingData: GreetingData) {
? ? ? ? let greeting = greetingData.greeting + " " + greetingData.subject
? ? ? ? self.view.setGreeting(greeting)
? ? }
}
class GreetingViewController : UIViewController, GreetingView {
? ? var eventHandler: 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
```
再一次,回到功能:
* Distribution?—?undoubtedly, VIPER is a champion in distribution of responsibilities.
分配 - 無疑疗垛,VIPER是責任分配的倡導者症汹。
* Testability —no surprises here, better distribution?—?better testability.
可測試性 - 這里沒有驚喜,更好的分布 - 更好的可測試性贷腕。
* Easy of use?—?finally, two above come in cost of maintainability as you already guessed. You have to write huge amount of interface for classes with very small responsibilities.
易于使用 - 最后背镇,正如您已經(jīng)猜到的那樣,兩個以上的可維護性成本泽裳。 你必須為責任非常小的類編寫大量的接口瞒斩。
#So what about LEGO?
While using VIPER, you might feel like building The Empire State Building from LEGO blocks, and that is a signal that you have a problem. Maybe, it’s too early to adopt VIPER for your application and you should consider something simpler. Some people ignore this and continue shooting out of cannon into sparrows. I assume they believe that their apps will benefit from VIPER at least in the future, even if now the maintenance cost is unreasonably high. If you believe the same, then I’d recommend you to try [Generamba](https://github.com/rambler-digital-solutions/Generamba)?—?a tool for generating VIPER skeletons. Although for me personally it feels like using an automated targeting system for cannon instead of simply taking a sling shot.
在使用VIPER時,您可能會感覺要從LEGO塊構(gòu)建帝國大廈涮总,這是一個您遇到問題的信號胸囱。 也許,為您的應用程序采用VIPER為時尚早瀑梗,您應該考慮更簡單的事情烹笔。 有些人忽略了這一點,繼續(xù)從大炮射入麻雀抛丽。 我認為他們相信他們的應用程序至少在未來會受益于VIPER箕宙,即使現(xiàn)在維護成本過高也是如此。 如果你相信铺纽,那么我建議你嘗試Generamba? - 一個生成VIPER骨架的工具柬帕。 雖然對我個人來說,感覺就像使用大炮的自動瞄準系統(tǒng),而不是簡單地采取吊索射擊陷寝。
Conclusion
We went though several architectural patterns, and I hope you have found some answers to what bothered you, but I have no doubt that you realised that there is no silver bullet so choosing architecture pattern is a matter of weighting tradeoffs in your particular situation.
Therefore, it is natural to have a mix of architectures in same app. For example: you’ve started with MVC, then you realised that one particular screen became too hard to maintain efficiently with the MVC and switched to the MVVM, but only for this particular screen. There is no need to refactor other screens for which the MVC actually does work fine, because both of architectures are easily compatible.