[譯]iOS Architecture Patterns(iOS架構(gòu)模式)

原文地址

揭開MVC,MVP,MVVM和VIPER的神秘面紗


Translator-CL

Don’t miss the iOS Developer Roadmap for 2018!

在iOS中用MVC時(shí)感覺怎么樣? 有關(guān)切換到MVVM的疑問? 聽說(shuō)VIPER,但不確定它是否值得?

您即將在iOS環(huán)境中構(gòu)建有關(guān)架構(gòu)模式的知識(shí)。 我們將簡(jiǎn)要回顧一些流行的,并在理論和實(shí)踐上對(duì)它們進(jìn)行比較,僅舉幾個(gè)小例子。

掌握設(shè)計(jì)模式可能會(huì)讓人上癮,所以要注意:現(xiàn)在,在閱讀本文之前,您可能最終會(huì)問自己更多問題,例如:

Who supposed to own networking request: a Model or a Controller?

誰(shuí)應(yīng)該擁有網(wǎng)絡(luò)請(qǐng)求:模型或控制器?

How do I pass a Model into a View Model of a new View?

如何將模型傳遞到新視圖的視圖模型?

Who creates a new VIPER module: Router or Presenter?

誰(shuí)創(chuàng)建了一個(gè)新的VIPER模塊:路由器或演示者?


#Why care about choosing the architecture?

因?yàn)槿绻悴贿@樣做,有一天,用幾十種不同的東西調(diào)試一個(gè)龐大的class,你會(huì)發(fā)現(xiàn)自己無(wú)法找到并修復(fù)你class上的任何錯(cuò)誤?!?當(dāng)然,很難將這個(gè)class視為整個(gè)實(shí)體,因此,你總是會(huì)遺漏一些重要的細(xì)節(jié)。 如果您的應(yīng)用程序已經(jīng)處于這種情況,則很可能是:

1、This class is the UIViewController subclass.這個(gè)類是UIViewController的子類。

2、Your data stored directly in the UIViewController您的數(shù)據(jù)直接存儲(chǔ)在UIViewController中

3、Your UIViews do almost nothing你的UIViews什么也沒做

4、The Model is a dumb data structure模型是一個(gè)愚蠢的數(shù)據(jù)結(jié)構(gòu)

5、Your Unit Tests cover nothing你的單元測(cè)試什么也沒有

即使您遵循Apple的指導(dǎo)方針并實(shí)施Apple的MVC模式,這種情況也可能發(fā)生,所以不要感到難過(guò)。 Apple的MVC有問題,但我們稍后會(huì)再回過(guò)頭來(lái)看看。

讓我們定義一個(gè)好架構(gòu)的功能:

1、Balanced distribution of responsibilities among entities with strict roles.

在具有嚴(yán)格作用的實(shí)體之間平衡分配責(zé)任。

2、Testability usually comes from the first feature (and don’t worry: it is easy with appropriate architecture).

可測(cè)試性通常來(lái)自第一個(gè)功能(不用擔(dān)心:使用適當(dāng)?shù)募軜?gòu)很容易)。

3、Ease of use and a low maintenance cost.

易于使用,維護(hù)成本低。

#Why Distribution?

為何分配?

在我們?cè)噲D弄清楚事情是如何運(yùn)作的時(shí)候,分配對(duì)我們的大腦負(fù)有相當(dāng)大的負(fù)擔(dān)。 如果你認(rèn)為你的開發(fā)越多,你的大腦就越能適應(yīng)理解復(fù)雜性,那么你就是對(duì)的。 但是這種能力不能線性擴(kuò)展并且很快達(dá)到上限。 因此,打敗復(fù)雜性的最簡(jiǎn)單方法是按照單一責(zé)任原則(Single responsibility principle)在多個(gè)實(shí)體之間劃分責(zé)任。

為什么可測(cè)性?

對(duì)于那些已經(jīng)對(duì)單元測(cè)試表示感謝的人來(lái)說(shuō),這通常不是問題,單元測(cè)試在添加新功能后失敗或者由于重構(gòu)了一些復(fù)雜的類。 這意味著測(cè)試使這些開發(fā)人員免于在運(yùn)行時(shí)發(fā)現(xiàn)問題,這可能發(fā)生在應(yīng)用程序位于用戶設(shè)備上并且修復(fù)需要一周時(shí)間才能到達(dá)用戶時(shí)。

為什么易于使用?

這不需要答案,但值得一提的是,最好的代碼是從未編寫過(guò)的代碼。 因此,您擁有的代碼越少,您擁有的錯(cuò)誤就越少。 這意味著編寫更少代碼的愿望永遠(yuǎn)不應(yīng)僅僅由開發(fā)人員的懶惰來(lái)解釋,而且您不應(yīng)該傾向于關(guān)注維護(hù)成本的智能解決方案。

MV(X)要領(lǐng)

現(xiàn)在,在架構(gòu)設(shè)計(jì)模式方面,我們有很多選擇:

* MVC

* MVP

* MVVM

* VIPER

前三個(gè)假設(shè)將應(yīng)用程序的實(shí)體分為以下三類:

Models - 負(fù)責(zé)域數(shù)據(jù)或操作數(shù)據(jù)的數(shù)據(jù)訪問層,思考'Person'或'PersonDataProvider'類。

Views - 負(fù)責(zé)表示層(GUI),對(duì)于iOS環(huán)境,可以思考以“UI”前綴開頭的所有內(nèi)容。

Controller / Presenter / ViewModel? - 模型和視圖之間的粘合劑或介體,通常負(fù)責(zé)通過(guò)響應(yīng)用戶在View上執(zhí)行的操作以及使用模型更改更新視圖來(lái)更改模型。

實(shí)體劃分允許我們:

* 更好地理解它們(我們已經(jīng)知道)

* 重用它們(主要適用于視圖和模型)

* 獨(dú)立測(cè)試它們

讓我們從MV(X)模式開始,稍后再回到VIPER。

#MVC

以前是怎樣的

在討論Apple的MVC愿景之前,讓我們來(lái)看看傳統(tǒng)的MVC。


在這種情況下,View是無(wú)狀態(tài)的。 一旦Model改變,它就由Controller簡(jiǎn)單地渲染。 一旦按下鏈接導(dǎo)航到其他地方,就可以想象網(wǎng)頁(yè)完全重新加載。 盡管可以在iOS應(yīng)用程序中實(shí)現(xiàn)傳統(tǒng)的MVC,但由于架構(gòu)問題,它沒有多大意義 - 所有三個(gè)實(shí)體都緊密耦合,每個(gè)實(shí)體都知道其他兩個(gè)實(shí)體。 這大大降低了每個(gè)人的可重用性 - 這不是您希望在應(yīng)用程序中擁有的內(nèi)容。 出于這個(gè)原因,我們甚至試圖編寫一個(gè)規(guī)范的MVC示例。

`Traditional MVC doesn't seems to be applicable to modern iOS development.`

#Apple’s MVC

Expectation


Controller是View和Model之間的中介,因此他們不了解彼此。 最不可重復(fù)使用的是Controller,這通常對(duì)我們來(lái)說(shuō)很好,因?yàn)槲覀儽仨氂幸粋€(gè)適合所有那些不適合模型的棘手業(yè)務(wù)邏輯的地方。

從理論上講,它看起來(lái)很簡(jiǎn)單,但你覺得有些不對(duì)勁,對(duì)吧? 你甚至聽說(shuō)過(guò)人們將MVC簡(jiǎn)化為大規(guī)模視圖控制器。 而且,視圖控制器卸載成為iOS開發(fā)人員的一個(gè)重要主題。 Apple剛剛采用傳統(tǒng)的MVC并對(duì)其進(jìn)行了一些改進(jìn),為什么會(huì)發(fā)生這種情況呢?

Reality


Cocoa MVC鼓勵(lì)您編寫Massive View控制器,因?yàn)樗鼈儏⑴c了View的生命周期,很難說(shuō)它們是分開的。 雖然您仍然可以將一些業(yè)務(wù)邏輯和數(shù)據(jù)轉(zhuǎn)換卸載到模型,但是在將工作卸載到View時(shí)沒有太多選擇,在大多數(shù)情況下,View的所有責(zé)任都是發(fā)送操作 到控制器。 視圖控制器最終成為所有事物的委托和數(shù)據(jù)源,并且通常負(fù)責(zé)調(diào)度和取消網(wǎng)絡(luò)請(qǐng)求,并且......您可以對(duì)其進(jìn)行命名。

你有多少次見過(guò)這樣的代碼:

```

var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell

userCell.configureWithUser(user)

```

這個(gè)cell,即直接使用模型配置的View,因此違反了MVC指南,但這種情況一直在發(fā)生,通常人們不會(huì)覺得它是錯(cuò)誤的。 如果您嚴(yán)格遵循MVC,那么您應(yīng)該從控制器配置單元格,并且不要將模型傳遞到View中,這將進(jìn)一步增加Controller的大小。

##Cocoa MVC合理地縮寫為Massive View Controller。

在進(jìn)行單元測(cè)試之前,問題可能并不明顯(希望在項(xiàng)目中確實(shí)如此)。 由于視圖控制器與視圖緊密耦合,因此在測(cè)試視圖及其生命周期時(shí)必須非常有創(chuàng)意,同時(shí)以這種方式編寫視圖控制器的代碼,使業(yè)務(wù)邏輯分離得太多 盡可能從視圖布局代碼。

我們來(lái)看看簡(jiǎn)單的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組裝

這似乎不太可測(cè)試,對(duì)嗎? 我們可以將問候的生成移動(dòng)到新的GreetingModel類中并單獨(dú)測(cè)試它,但是我們無(wú)法在GreetingViewController內(nèi)部測(cè)試任何表示邏輯(盡管上面的例子中沒有太多這樣的邏輯)而沒有直接調(diào)用UIView相關(guān)的方法( viewDidLoad,didTapButton)可能會(huì)導(dǎo)致加載所有視圖,這對(duì)單元測(cè)試很不利。

實(shí)際上,在一個(gè)模擬器(例如iPhone 4S)上加載和測(cè)試UIViews并不能保證它在其他設(shè)備(例如iPad)上正常工作,因此我建議從單元測(cè)試目標(biāo)配置中刪除“Host Application” 并在沒有應(yīng)用程序在模擬器上運(yùn)行的情況下運(yùn)行測(cè)試。

##單元測(cè)試不能真正測(cè)試視圖和控制器之間的交互

盡管如此,Cocoa MVC似乎是一個(gè)非常糟糕的選擇模式。 但是,讓我們根據(jù)本文開頭定義的功能對(duì)其進(jìn)行評(píng)估:

* Distribution?—?the View and the Model in fact separated, but the View and the Controller are tightly coupled.

視圖和模型實(shí)際上是分開的,但視圖和控制器是緊密耦合的。

* Testability?—?due to the bad distribution you’ll probably only test your Model.由于分布不良,您可能只測(cè)試您的模型。

* 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.其他模式中代碼量最少。 此外,每個(gè)人都熟悉它,因此,即使是沒有經(jīng)驗(yàn)的開發(fā)人員也能輕松維護(hù)它。

如果您還沒準(zhǔn)備好在您的架構(gòu)上投入更多時(shí)間,那么Cocoa MVC就是您選擇的模式,并且您覺得維護(hù)成本較高的東西對(duì)于您的小型寵物項(xiàng)目來(lái)說(shuō)太過(guò)分了。

總結(jié):就開發(fā)速度而言,Cocoa MVC是最好的架構(gòu)模式。

#MVP

Cocoa MVC’s promises delivered


它看起來(lái)不像Apple的MVC嗎? 是的,確實(shí)如此,它的名字是MVP(Passive View變體)。 但是等一下......這是否意味著Apple的MVC實(shí)際上是MVP? 不,它不是,因?yàn)槿绻氵€記得那里,View與Controller緊密耦合,而MVP的中介Presenter與視圖控制器的生命周期無(wú)關(guān),而且View可以很容易地被嘲笑,所以 Presenter中根本沒有布局代碼,但它負(fù)責(zé)使用數(shù)據(jù)和狀態(tài)更新View。


如果我告訴你,UIViewController就是View。

就MVP而言,UIViewController子類實(shí)際上是Views而不是Presenters。 這種區(qū)別提供了極好的可測(cè)試性,這是以開發(fā)速度為代價(jià)的,因?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 {

? ? 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是第一個(gè)揭示由于具有三個(gè)實(shí)際上分離的層而發(fā)生的組裝問題的模式。

由于我們不希望View了解Model,因此在呈現(xiàn)視圖控制器(即View)中執(zhí)行匯編是不對(duì)的,因此我們必須在其他地方執(zhí)行。 例如,我們可以制作應(yīng)用程序范圍的Router服務(wù),該服務(wù)將負(fù)責(zé)執(zhí)行程序集和View-to-View演示。 這個(gè)問題出現(xiàn)了,不僅要在MVP中解決,還要在以下所有模式中解決。

讓我們來(lái)看看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).分布 - 我們?cè)赑resenter和模型之間分配了大部分職責(zé),其中啞視圖(在模型上面的示例中也是愚蠢的)。

* Testability?—?is excellent, we can test most of the business logic due to the dumb View.可測(cè)試性 - 非常好,我們可以測(cè)試大多數(shù)業(yè)務(wù)邏輯,因?yàn)樗鼈兪菃∫晥D。

* 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.

易于使用 - 在我們不切實(shí)際的簡(jiǎn)單示例中,與MVC相比,代碼量增加了一倍,但與此同時(shí),MVP的概念非常清晰。

總結(jié):iOS中的MVP意味著極好的可測(cè)試性和大量代碼。

#MVP

With Bindings and Hooters

還有MVP的另一種風(fēng)格 - 監(jiān)督控制器MVP。 此變體包括View和Model的直接綁定,而Presenter(監(jiān)督控制器)仍然可以處理來(lái)自View的操作,并且能夠更改View。


但正如我們之前已經(jīng)知道的那樣,模糊的責(zé)任分離是不好的,以及視圖和模型的緊密耦合。 這類似于Cocoa桌面開發(fā)中的工作方式。

與傳統(tǒng)的MVC相同,我沒有看到為有缺陷的架構(gòu)編寫示例的重點(diǎn)。

#MVVM

The latest and the greatest of the MV(X) kind

MVVM是最新的MV(X)類型,因此,我們希望它考慮到MV(X)之前面臨的問題。

從理論上講,Model-View-ViewModel看起來(lái)非常好。 View和Model已經(jīng)為我們所熟悉,但也是Mediator,表示為View Model。


它與MVP非常相似:

* MVVM將視圖控制器視為視圖

* View和Model之間沒有緊密耦合

此外,它確實(shí)像MVP的監(jiān)督版本一樣綁定; 但是,這次不在View和Model之間,而是在View和View Model之間。

那么iOS現(xiàn)實(shí)中的View Model是什么? 它基本上是UIKit獨(dú)立表示您的View及其狀態(tài)。 視圖模型調(diào)用模型中的更改并使用更新的模型更新自身,并且由于我們?cè)谝晥D和視圖模型之間有綁定,因此第一個(gè)更新。

Bindings

我在MVP部分簡(jiǎn)要提到過(guò)它們,但我們?cè)谶@里討論它們。 綁定開箱即用于OS X開發(fā),但我們?cè)趇OS工具箱中沒有它們。 當(dāng)然我們有KVO和通知,但它們不如綁定方便。

所以,如果我們不想自己編寫,我們有兩個(gè)選擇:

* 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).

事實(shí)上,如今,如果你聽到“MVVM” - 你認(rèn)為ReactiveCocoa,反之亦然。 盡管可以使用簡(jiǎn)單綁定構(gòu)建MVVM,但ReactiveCocoa(或兄弟姐妹)將允許您獲得大部分MVVM。

關(guān)于反應(yīng)性框架有一個(gè)痛苦的事實(shí):強(qiáng)大的力量伴隨著巨大的責(zé)任。 當(dāng)你被動(dòng)反應(yīng)時(shí),很容易弄亂事情。 換句話說(shuō),如果你做錯(cuò)了什么,你可能會(huì)花很多時(shí)間調(diào)試應(yīng)用程序,所以只需看看這個(gè)調(diào)用堆棧。


在我們的簡(jiǎn)單示例中,F(xiàn)RF框架甚至KVO都是一種矯枉過(guò)正,相反,我們將明確要求View Model使用showGreeting方法進(jìn)行更新,并使用greetingDidChange回調(diào)函數(shù)的簡(jiǎn)單屬性。

```

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

```

再次回到我們的功能評(píng)估:

* 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.

分布 - 在我們的小例子中并不清楚,但事實(shí)上,MVVM的View比MVP的View有更多的責(zé)任。 因?yàn)榈谝粋€(gè)通過(guò)設(shè)置綁定從View Model更新它的狀態(tài),而第二個(gè)只是將所有事件轉(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.

可測(cè)試性 -? View Model對(duì)View一無(wú)所知,這使我們可以輕松地測(cè)試它。 View也可能已經(jīng)過(guò)測(cè)試,但由于它依賴于UIKit,您可能希望跳過(guò)它。

* 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ù)量的代碼,但在真實(shí)的應(yīng)用程序中,您必須將所有事件從View轉(zhuǎn)發(fā)到Presenter并手動(dòng)更新View,MVVM將更加出色 如果你使用綁定。

總結(jié):MVVM非常有吸引力,因?yàn)樗Y(jié)合了上述方法的優(yōu)點(diǎn),此外,由于View端的綁定,它不需要額外的View更新代碼。 盡管如此,可測(cè)試性仍處于良好水平。

#VIPER

樂高的建筑體驗(yàn)轉(zhuǎn)移到iOS應(yīng)用程序設(shè)計(jì)中

VIPER是我們的最后一位候選人,特別有趣,因?yàn)樗皇莵?lái)自MV(X)類別。

到目前為止,您必須同意責(zé)任的粒度非常好。 VIPER對(duì)分離職責(zé)的想法進(jìn)行了另一次迭代,這次我們有五個(gè)層次。


* 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ù)(實(shí)體)或網(wǎng)絡(luò)相關(guān)的業(yè)務(wù)邏輯,如創(chuàng)建實(shí)體的新實(shí)例或從服務(wù)器獲取實(shí)體。 出于這些目的,您將使用一些服務(wù)和管理器,這些服務(wù)和管理器不被視為VIPER模塊的一部分,而是外部依賴項(xiàng)。

* Presenter?—?contains the UI related (but UIKit independent) business logic, invokes methods on the Interactor.? ?

Presenter? - 包含UI相關(guān)(但UIKit獨(dú)立)業(yè)務(wù)邏輯,調(diào)用Interactor上的方法.

* Entities?—?your plain data objects, not the data access layer, because that is a responsibility of the Interactor.

實(shí)體 - 您的普通數(shù)據(jù)對(duì)象,而不是數(shù)據(jù)訪問層,因?yàn)檫@是交互者的責(zé)任。

* Router?—?responsible for the segues between the VIPER modules.

路由器 - 負(fù)責(zé)VIPER模塊之間的隔離。

基本上,VIPER模塊可以是一個(gè)屏幕或應(yīng)用程序的整個(gè)user story - 想一想身份驗(yàn)證,可以是一個(gè)屏幕或幾個(gè)相關(guān)的屏幕。 你的“樂高”積木有多??? - 隨你便。

如果我們將它與MV(X)類型進(jìn)行比較,我們會(huì)看到責(zé)任分布的一些差異:

* Model (data interaction) logic shifted into the Interactor with the Entities as dumb data structures.

模型(數(shù)據(jù)交互)邏輯移入Interactor,實(shí)體作為啞數(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表示職責(zé)移入了Presenter,而不是數(shù)據(jù)更改功能。

* VIPER is the first pattern which explicitly addresses navigation responsibility, which is supposed to be resolved by the Router.

VIPER是第一個(gè)明確解決導(dǎo)航責(zé)任的模式,它應(yīng)該由路由器解決。

正確的路由方式對(duì)iOS應(yīng)用程序來(lái)說(shuō)是一個(gè)挑戰(zhàn),MV(X)模式根本無(wú)法解決這個(gè)問題。

該示例不包括模塊之間的路由或交互,因?yàn)镸V(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.

分配 - 無(wú)疑,VIPER是責(zé)任分配的倡導(dǎo)者。

* Testability —no surprises here, better distribution?—?better testability.

可測(cè)試性 - 這里沒有驚喜,更好的分布 - 更好的可測(cè)試性。

* 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)猜到的那樣,兩個(gè)以上的可維護(hù)性成本。 你必須為責(zé)任非常小的類編寫大量的接口。

#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時(shí),您可能會(huì)感覺要從LEGO塊構(gòu)建帝國(guó)大廈,這是一個(gè)您遇到問題的信號(hào)。 也許,為您的應(yīng)用程序采用VIPER為時(shí)尚早,您應(yīng)該考慮更簡(jiǎn)單的事情。 有些人忽略了這一點(diǎn),繼續(xù)從大炮射入麻雀。 我認(rèn)為他們相信他們的應(yīng)用程序至少在未來(lái)會(huì)受益于VIPER,即使現(xiàn)在維護(hù)成本過(guò)高也是如此。 如果你相信,那么我建議你嘗試Generamba? - 一個(gè)生成VIPER骨架的工具。 雖然對(duì)我個(gè)人來(lái)說(shuō),感覺就像使用大炮的自動(dòng)瞄準(zhǔn)系統(tǒng),而不是簡(jiǎn)單地采取吊索射擊。

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.

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容