iOS 架構(gòu)模式 - 簡述 MVC, MVP, MVVM 和 VIPER (譯)

Make everything as simple as possible, but not simpler?—?Albert Einstein
把每件事活翩,做簡單到極致,但又不過于簡單 - 阿爾伯特·愛因斯坦

在使用 iOS 的 MVC 時候感覺怪怪的缆瓣?想要嘗試下 MVVM喧枷?之前聽說過 VIPER,但是又糾結(jié)是不是值得去學弓坞?

繼續(xù)閱讀隧甚,你就會知道上面問題的答案 - 如果讀完了還是不知道的話,歡迎留言評論渡冻。

iOS 上面的架構(gòu)模式你可能之前就了解過一些呻逆,接下來我們會幫你把它們進行一下梳理。我們先簡要回顧一下目前比較主流的架構(gòu)模式菩帝,分析比較一些他們的原理咖城,并用一些小栗子來進行練習茬腿。如果你對其中的某一種比較感興趣的話,我們也在文章里面給出了對應的鏈接宜雀。

對于設計模式的學習是一件容易上癮的事情切平,所以先提醒你一下:在你讀完這篇文章之后,可能會比讀之前有更多的疑問辐董,比如:

(MVC)誰來負責網(wǎng)絡請求:是 Model 還是 Controller悴品?

(MVVM)我該怎么去把一個 Model 傳遞給一個新創(chuàng)建的 View 的 ViewModel?

(VIPER)誰來負責創(chuàng)建 VIPER 模塊:是 Router 還是 Presenter简烘?

為何要在意架構(gòu)的選擇呢苔严?
因為如果你不在意的話,難保一天孤澎,你就需要去調(diào)試一個巨大無比又有著各種問題的類届氢,然后你會發(fā)現(xiàn)在這個類里面,你完全就找不到也修復不了任何 bug覆旭。一般來說退子,把這么大的一個類作為整體放在腦子里記著是一件非常困難的事情,你總是難免會忘掉一些比較重要的細節(jié)型将。如果你發(fā)現(xiàn)在你的應用里面已經(jīng)開始出現(xiàn)這種狀況了寂祥,那你很可能遇到過下面這類問題:

這個類是一個 UIViewController 的子類。
你的數(shù)據(jù)直接保存在了 UIViewController 里面七兜。
你的 UIViews 好像什么都沒做丸凭。
你的 Model 只是一個純粹的數(shù)據(jù)結(jié)構(gòu)
你的單元測試什么都沒有覆蓋到
其實即便你遵循了 Apple 的設計規(guī)范,實現(xiàn)了 Apple 的 MVC 框架腕铸,也還是一樣會遇到上面這些問題惜犀;所以也沒什么好失落的。Apple 的 MVC 框架 有它自身的缺陷恬惯,不過這個我們后面再說。

讓我們先來定義一下好的框架應該具有的特征:

用嚴格定義的角色亚茬,平衡的將職責 劃分 給不同的實體酪耳。
可測性 通常取決于上面說的第一點(不用太擔心,如果架構(gòu)何時的話刹缝,做到這點并不難)碗暗。
易用 并且維護成本低。
為什么要劃分梢夯?
當我們試圖去理解事物的工作原理的時候言疗,劃分可以減輕我們的腦部壓力。如果你覺得開發(fā)的越多颂砸,大腦就越能適應去處理復雜的工作噪奄,確實是這樣死姚。但是大腦的這種能力不是線性提高的,而且很快就會達到一個瓶頸勤篮。所以要處理復雜的事情都毒,最好的辦法還是在遵循 單一責任原則 的條件下,將它的職責劃分到多個實體中去碰缔。

為什么要可測性账劲?
對于那些對單元測試心存感激的人來說,應該不會有這方面的疑問:單元測試幫助他們測試出了新功能里面的錯誤金抡,或者是幫他們找出了重構(gòu)的一個復雜類里面的 bug瀑焦。這意味著這些單元測試幫助這些開發(fā)者們在程序運行之前就發(fā)現(xiàn)了問題,這些問題如果被忽視的話很可能會提交到用戶的設備上去梗肝;而修復這些問題榛瓮,又至少需要一周左右的時間(AppStore 審核)。

為什么要易用
這塊沒什么好說的统捶,直說一點:最好的代碼是那些從未被寫出來的代碼榆芦。代碼寫的越少,問題就越少喘鸟;所以開發(fā)者想少寫點代碼并不一定就是因為他懶匆绣。還有,當你想用一個比較 聰明 的方法的時候什黑,全完不要忽略了它的維護成本崎淳。

MV(X) 的基本要素
現(xiàn)在我們面對架構(gòu)設計模式的時候有了很多選擇:

MVC
MVP
MVVM
VIPER
首先前三種模式都是把所有的實體歸類到了下面三種分類中的一種:

Models(模型)?—?數(shù)據(jù)層,或者負責處理數(shù)據(jù)的 數(shù)據(jù)接口層愕把。比如 Person 和 PersonDataProvider 類
Views(視圖) - 展示層(GUI)拣凹。對于 iOS 來說所有以 UI 開頭的類基本都屬于這層。
Controller/Presenter/ViewModel(控制器/展示器/視圖模型) - 它是 Model 和 View 之間的膠水或者說是中間人恨豁。一般來說嚣镜,當用戶對 View 有操作時它負責去修改相應 Model;當 Model 的值發(fā)生變化時它負責去更新對應 View橘蜜。
將實體進行分類之后我們可以:

更好的理解
重用(主要是 View 和 Model)
對它們獨立的進行測試
讓我從 MV(X) 系列開始講起菊匿,最后講 VIPER。

MVC - 它原來的樣子

93c3aeab-92c0-4cc0-8344-969b668fe76b.png

在開始討論 Apple 的 MVC 之前计福,我們先來看下 傳統(tǒng)的 MVC跌捆。

在這種架構(gòu)下,View 是無狀態(tài)的象颖,在 Model 變化的時候它只是簡單的被 Controller 重繪佩厚;就像網(wǎng)頁一樣,點擊了一個新的鏈接说订,整個網(wǎng)頁就重新加載抄瓦。盡管這種架構(gòu)可以在 iOS 應用里面實現(xiàn)潮瓶,但是由于 MVC 的三種實體被緊密耦合著,每一種實體都和其他兩種有著聯(lián)系闺鲸,所以即便是實現(xiàn)了也沒有什么意義筋讨。這種緊耦合還戲劇性的減少了它們被重用的可能,這恐怕不是你想要在自己的應用里面看到的摸恍。綜上悉罕,傳統(tǒng) MVC 的例子我覺得也沒有必要去寫了。

傳統(tǒng)的 MVC 已經(jīng)不適合當下的 iOS 開發(fā)了立镶。

Apple 的 MVC
理想

8d779f6a-265b-43c3-90be-dc9997b9963d.png

View 和 Model 之間是相互獨立的壁袄,它們只通過 Controller 來相互聯(lián)系。有點惱人的是 Controller 是重用性最差的媚媒,因為我們一般不會把冗雜的業(yè)務邏輯放在 Model 里面嗜逻,那就只能放在 Controller 里了。

理論上看這么做貌似挺簡單的缭召,但是你有沒有覺得有點不對勁栈顷?你甚至聽過有人把 MVC 叫做重控制器模式。另外 關于 ViewController 瘦身 已經(jīng)成為 iOS 開發(fā)者們熱議的話題了嵌巷。為什么 Apple 要沿用只是做了一點點改進的傳統(tǒng) MVC 架構(gòu)呢萄凤?

現(xiàn)實

ad9bafec-2e16-4725-abe5-4e7fde2e0877.png

Cocoa MVC 鼓勵你去寫重控制器是因為 View 的整個生命周期都需要它去管理,Controller 和 View 很難做到相互獨立搪哪。雖然你可以把控制器里的一些業(yè)務邏輯和數(shù)據(jù)轉(zhuǎn)換的工作交給 Model靡努,但是你再想把負擔往 View 里面分攤的時候就沒辦法了;因為 View 的主要職責就只是講用戶的操作行為交給 Controller 去處理而已晓折。于是 ViewController 最終就變成了所有東西的代理和數(shù)據(jù)源惑朦,甚至還負責網(wǎng)絡請求的發(fā)起和取消,還有...剩下的你來講漓概。

像下面這種代碼你應該不陌生吧:

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

Cell 作為一個 View 直接用 Model 來完成了自身的配置漾月,MVC 的原則被打破了,這種情況一直存在胃珍,而且還沒人覺得有什么問題梁肿。如果你是嚴格遵循 MVC 的話,你應該是在 ViewController 里面去配置 Cell堂鲜,而不是直接將 Model 丟給 Cell栈雳,當然這樣會讓你的 ViewController 更重护奈。

Cocoa MVC 被戲稱為重控制器模式還是有原因的缔莲。

問題直到開始 單元測試(希望你的項目里面已經(jīng)有了)之后才開始顯現(xiàn)出來。Controller 測試起來很困難霉旗,因為它和 View 耦合的太厲害痴奏,要測試它的話就需要頻繁的去 mock View 和 View 的生命周期蛀骇;而且按照這種架構(gòu)去寫控制器代碼的話,業(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;

MVC 的組裝擅憔,可以放在當前正在顯示的 ViewController 里面

這段代碼看起來不太好測試對吧?我們可以把 greeting 的生成方法放到一個新類 GreetingModel 里面去單獨測試檐晕。但是我們?nèi)绻徽{(diào)用與 View 相關的方法的話 (viewDidLoad, didTapButton)暑诸,就測試不到 GreetingViewController 里面任何的顯示邏輯(雖然在上面這個例子里面,邏輯已經(jīng)很少了)辟灰;而調(diào)用的話就可能需要把所有的 View 都加載出來个榕,這對單元測試來說太不利了。

實際上芥喇,在模擬器(比如 iPhone 4S)上運行并測試 View 的顯示并不能保證在其他設備上(比如 iPad)也能良好運行西采。所以我建議把「Host Application」從你的單元測試配置項里移除掉,然后在不啟動模擬器的情況下去跑你的單元測試继控。

View 和 Controller 之間的交互械馆,并不能真正的被單元測試覆蓋。

綜上所述武通,Cocoa MVC 貌似并不是一個很好的選擇霹崎。但是我們還是評估一下他在各方面的表現(xiàn)(在文章開頭有講):

劃分 - View 和 Model 確實是實現(xiàn)了分離,但是 View 和 Controller 耦合的太厲害
可測性 - 因為劃分的不夠清楚厅须,所以能測的基本就只有 Model 而已
易用 - 相較于其他模式仿畸,它的代碼量最少。而且基本上每個人都很熟悉它朗和,即便是沒太多經(jīng)驗的開發(fā)者也能維護错沽。
在這種情況下你可以選擇 Cocoa MVC:你并不想在架構(gòu)上花費太多的時間,而且你覺得對于你的小項目來說眶拉,花費更高的維護成本只是浪費而已千埃。

如果你最看重的是開發(fā)速度,那么 Cocoa MVC 就是你最好的選擇忆植。

MVP - 保證了職責劃分的(promises delivered) Cocoa MVC

d8ad72b3-f150-4988-af6f-0db785c40793.png

看起來確實很像 Apple 的 MVC 對吧放可?確實蠻像,它的名字是 MVP(被動變化的 View)朝刊。稍等...這個意思是說 Apple 的 MVC 實際上是 MVP 嗎耀里?不是的,回想一下拾氓,在 MVC 里面 View 和 Controller 是耦合緊密的冯挎,但是對于 MVP 里面的 Presenter 來講,它完全不關注 ViewController 的生命周期咙鞍,而且 View 也能被簡單 mock 出來房官,所以在 Presenter 里面基本沒什么布局相關的代碼趾徽,它的職責只是通過數(shù)據(jù)和狀態(tài)更新 View。

如果我跟你講 UIViewController 在這里的角色其實是 View 你感覺如何翰守。

在 MVP 架構(gòu)里面孵奶,UIViewController 的那些子類其實是屬于 View 的,而不是 Presenter蜡峰。這種區(qū)別提供了極好的可測性了袁,但是這是用開發(fā)速度的代價換來的,因為你必須要手動的去創(chuà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

關于組裝方面的重要說明
MVP 架構(gòu)擁有三個真正獨立的分層早像,所以在組裝的時候會有一些問題,而 MVP 也成了第一個披露了這種問題的架構(gòu)肖爵。因為我們不想讓 View 知道 Model 的信息卢鹦,所以在當前的 ViewController(角色其實是 View)里面去進行組裝肯定是不正確的,我們應該在另外的地方完成組裝劝堪。比如冀自,我們可以創(chuàng)建一個應用層(app-wide)的 Router 服務,讓它來負責組裝和 View-to-View 的轉(zhuǎn)場秒啦。這個問題不僅在 MVP 中存在熬粗,在接下來要介紹的模式里面也都有這個問題。

讓我們來看一下 MVP 在各方面的表現(xiàn):

劃分 - 我們把大部分的職責都分配到了 Presenter 和 Model 里面余境,而 View 基本上不需要做什么(在上面的例子里面驻呐,Model 也什么都沒做)。
可測性 - 簡直棒芳来,我們可以通過 View 來測試大部分的業(yè)務邏輯含末。
易用 - 就我們上面那個簡單的例子來講,代碼量差不多是 MVC 架構(gòu)的兩倍即舌,但是 MVP 的思路還是蠻清晰的佣盒。
MVP 架構(gòu)在 iOS 中意味著極好的可測性和巨大的代碼量。

MVP - 添加了數(shù)據(jù)綁定的另一個版本

41e82632-02fc-44f9-a0eb-ef32aae5818a.png

還存在著另一種的 MVP - Supervising Controller MVP顽聂。這個版本的 MVP 包括了 View 和 Model 的直接綁定肥惭,與此同時 Presenter(Supervising Controller)仍然繼續(xù)處理 View 上的用戶操作,控制 View 的顯示變化紊搪。

但是我們之前講過蜜葱,模糊的職責劃分是不好的事情,比如 View 和 Model 的緊耦合耀石。這個道理在 Cocoa 桌面應用開發(fā)上面也是一樣的牵囤。

就像傳統(tǒng) MVC 架構(gòu)一樣,我找不到有什么理由需要為這個有瑕疵的架構(gòu)寫一個例子。

MVVM - 是 MV(X) 系列架構(gòu)里面最新興的奔浅,也是最出色的

1b8ff549-4fa4-489a-adf3-e8ba52e6bb96.png

MVVM 架構(gòu)是 MV(X) 里面最新的一個,讓我們希望它在出現(xiàn)的時候已經(jīng)考慮到了 MV(X) 模式之前所遇到的問題吧诗良。

理論上來說汹桦,Model - View - ViewModel 看起來非常棒。View 和 Model 我們已經(jīng)都熟悉了鉴裹,中間人的角色我們也熟悉了舞骆,但是在這里中間人的角色變成了 ViewModel。

它跟 MVP 很像:

MVVM 架構(gòu)把 ViewController 看做 View径荔。
View 和 Model 之間沒有緊耦合
另外督禽,它還像 Supervising 版的 MVP 那樣做了數(shù)據(jù)綁定,不過這次不是綁定 View 和 Model总处,而是綁定 View 和 ViewModel狈惫。

那么,iOS 里面的 ViewModel 到底是個什么東西呢鹦马?本質(zhì)上來講胧谈,他是獨立于 UIKit 的, View 和 View 的狀態(tài)的一個呈現(xiàn)(representation)荸频。ViewModel 能主動調(diào)用對 Model 做更改菱肖,也能在 Model 更新的時候?qū)ψ陨磉M行調(diào)整,然后通過 View 和 ViewModel 之間的綁定旭从,對 View 也進行對應的更新稳强。

綁定
我在 MVP 的部分簡單的提過這個內(nèi)容,在這里讓我們再延伸討論一下和悦。綁定這個概念源于 OS X 平臺的開發(fā)退疫,但是在 iOS 平臺上面,我們并沒有對應的開發(fā)工具鸽素。當然蹄咖,我們也有 KVO 和 通知,但是用這些方式去做綁定不太方便付鹿。

那么澜汤,如果我們不想自己去寫他們的話,下面提供了兩個選擇:

選一個基于 KVO 的綁定庫舵匾,比如 RZDataBinding 或者 SwiftBond俊抵。
使用全量級的 函數(shù)式響應編程 框架,比如 ReactiveCocoa、RxSwift 或者 PromiseKit坐梯。
實際上徽诲,現(xiàn)在提到「MVVM」你應該就會想到 ReactiveCocoa,反過來也是一樣。雖然我們可以通過簡單的綁定來實現(xiàn) MVVM 模式谎替,但是 ReactiveCocoa(或者同類型的框架)會讓你更大限度的去理解 MVVM偷溺。

響應式編程框架也有一點不好的地方,能力越大責任越大嘛钱贯。用響應式編程用得不好的話挫掏,很容易會把事情搞得一團糟≈让或者這么說尉共,如果有什么地方出錯了,你需要花費更多的時間去調(diào)試弃锐“烙眩看著下面這張調(diào)用堆棧圖感受一下:

c8b080f0-d245-4628-a909-62d2deccf16c.png

在接下來的這個小例子中,用響應式框架(FRF)或者 KVO 都顯得有點大刀小用霹菊,所以我們用另一種方式:直接的調(diào)用 ViewModel 的 showGreeting 方法去更新自己(的 greeting 屬性)剧蚣,(在 greeting 屬性的 didSet 回調(diào)里面)用 greetingDidChange 閉包函數(shù)去更新 View 的顯示。

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

然后旋廷,我們再回過頭來對它各方面的表現(xiàn)做一個評價:

劃分 - 這在我們的小栗子里面表現(xiàn)的不是很清楚券敌,但是 MVVM 框架里面的 View 比 MVP 里面負責的事情要更多一些。因為前者是通過 ViewModel 的數(shù)據(jù)綁定來更新自身狀態(tài)的柳洋,而后者只是把所有的事件統(tǒng)統(tǒng)交給 Presenter 去處理就完了待诅,自己本身并不負責更新。
可測性 - 因為 ViewModel 對 View 是一無所知的熊镣,這樣我們對它的測試就變得很簡單卑雁。View 應該也是能夠被測試的,但是可能因為它對 UIKit 的依賴绪囱,你會直接略過它测蹲。
易用 - 在我們的例子里面,它的代碼量基本跟 MVP 持平鬼吵,但是在實際的應用當中 MVVM 會更簡潔一些扣甲。因為在 MVP 下你必須要把 View 的所有事件都交給 Presenter 去處理,而且需要手動的去更新 View 的狀態(tài)齿椅;而在 MVVM 下琉挖,你只需要用綁定就可以解決。
MVVM 真的很有魅力涣脚,因為它不僅結(jié)合了上述幾種框架的優(yōu)點示辈,還不需要你為視圖的更新去寫額外的代碼(因為在 View 上已經(jīng)做了數(shù)據(jù)綁定),另外它在可測性上的表現(xiàn)也依然很棒遣蚀。

VIPER - 把搭建樂高積木的經(jīng)驗應用到 iOS 應用的設計上


aaa8d85f-b66a-4bb2-a8f5-13fae704d3b1.png

VIPER 是我們最后一個要介紹的框架矾麻,這個框架比較有趣的是它不屬于任何一種 MV(X) 框架纱耻。

到目前為止,你可能覺得我們把職責劃分成三層险耀,這個顆粒度已經(jīng)很不錯了吧∨現(xiàn)在 VIPER 從另一個角度對職責進行了劃分,這次劃分了 五層甩牺。

Interactor(交互器) - 包括數(shù)據(jù)(Entities)或者網(wǎng)絡相關的業(yè)務邏輯蘑志。比如創(chuàng)建新的 entities 或者從服務器上獲取數(shù)據(jù);要實現(xiàn)這些功能柴灯,你可能會用到一些服務和管理(Services and Managers):這些可能會被誤以為成是外部依賴東西,但是它們就是 VIPER 的 Interactor 模塊费尽。
Presenter(展示器) - 包括 UI(but UIKit independent)相關的業(yè)務邏輯赠群,可以調(diào)用 Interactor 中的方法。
Entities(實體) - 純粹的數(shù)據(jù)對象旱幼。不包括數(shù)據(jù)訪問層查描,因為這是 Interactor 的職責。
Router(路由) - 負責 VIPER 模塊之間的轉(zhuǎn)場
實際上 VIPER 模塊可以只是一個頁面(screen)柏卤,也可以是你應用里整個的用戶使用流程(the whole user story)- 比如說「驗證」這個功能冬三,它可以只是一個頁面,也可以是連續(xù)相關的一組頁面缘缚。你的每個「樂高積木」想要有多大勾笆,都是你自己來決定的。

如果我們把 VIPER 和 MV(X) 系列做一個對比的話桥滨,我們會發(fā)現(xiàn)它們在職責劃分上面有下面的一些區(qū)別:

Model(數(shù)據(jù)交互)的邏輯被轉(zhuǎn)移到了 Interactor 里面窝爪,Entities 只是一個什么都不用做的數(shù)據(jù)結(jié)構(gòu)體。
Controller/Presenter/ViewModel 的職責里面齐媒,只有 UI 的展示功能被轉(zhuǎn)移到了 Presenter 里面蒲每。Presenter 不具備直接更改數(shù)據(jù)的能力。
VIPER 是第一個把導航的職責單獨劃分出來的架構(gòu)模式喻括,負責導航的就是 Router 層邀杏。
如何正確的使用導航(doing routing)對于 iOS 應用開發(fā)來說是一個挑戰(zhàn),MV(X) 系列的架構(gòu)完全就沒有意識到(所以也不用處理)這個問題唬血。

下面的這個列子并沒有涉及到導航和 VIPER 模塊間的轉(zhuǎn)場望蜡,同樣上面 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 {
    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

我們再來評價下它在各方面的表現(xiàn):

劃分 - 毫無疑問的拷恨,VIPER 在職責劃分方面是做的最好的泣特。
可測性 - 理所當然的,職責劃分的越好挑随,測試起來就越容易
易用 - 最后状您,你可能已經(jīng)猜到了勒叠,上面兩點好處都是用維護性的代價換來的。一個小小的任務膏孟,可能就需要你為各種類寫大量的接口眯分。
那么,我們到底應該給「樂高」一個怎樣的評價呢柒桑?
如果你在使用 VIPER 框架的時候有一種在用樂高積木搭建帝國大廈的感覺弊决,那么你可能 正在犯錯誤;可能對于你負責的應用來說魁淳,還沒有到使用 VIPER 的時候飘诗,你應該把一些事情考慮的再簡單一些〗绻洌總是有一些人忽視這個問題昆稿,繼續(xù)扛著大炮去打小鳥。我覺得可能是因為他們相信息拜,雖然目前來看維護成本高的不合常理溉潭,但是至少在將來他們的應用可以從 VIPER 架構(gòu)上得到回報吧。如果你也跟他們的觀點一樣的話少欺,那我建議你嘗試一下 Generamba - 一個可以生成 VIPER 框架的工具喳瓣。雖然對于我個人來講,這感覺就像給大炮裝上了一個自動瞄準系統(tǒng)赞别,然后去做一件只用彈弓就能解決的事情畏陕。

結(jié)論
我們簡單了解了幾種架構(gòu)模式,對于那些讓你困惑的問題仿滔,我希望你已經(jīng)找到了答案蹭秋。但是毫無疑問,你應該已經(jīng)意識到了堤撵,在選擇架構(gòu)模式這件問題上面仁讨,不存在什么 銀色子彈,你需要做的就是具體情況具體分析实昨,權(quán)衡利弊而已洞豁。

因此在同一個應用里面,即便有幾種混合的架構(gòu)模式也是很正常的一件事情荒给。比如:開始的時候丈挟,你用的是 MVC 架構(gòu),后來你意識到有一個特殊的頁面用 MVC 做的的話維護起來會相當?shù)穆闊┲镜纾贿@個時候你可以只針對這一個頁面用 MVVM 模式去開發(fā)曙咽,對于之前那些用 MVC 就能正常工作的頁面,你完全沒有必要去重構(gòu)它們挑辆,因為兩種架構(gòu)是完全可以和睦共存的例朱。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末孝情,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子洒嗤,更是在濱河造成了極大的恐慌箫荡,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,820評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件渔隶,死亡現(xiàn)場離奇詭異羔挡,居然都是意外死亡,警方通過查閱死者的電腦和手機间唉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,648評論 3 399
  • 文/潘曉璐 我一進店門绞灼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人呈野,你說我怎么就攤上這事低矮。” “怎么了际跪?”我有些...
    開封第一講書人閱讀 168,324評論 0 360
  • 文/不壞的土叔 我叫張陵商佛,是天一觀的道長喉钢。 經(jīng)常有香客問我姆打,道長,這世上最難降的妖魔是什么肠虽? 我笑而不...
    開封第一講書人閱讀 59,714評論 1 297
  • 正文 為了忘掉前任幔戏,我火速辦了婚禮,結(jié)果婚禮上税课,老公的妹妹穿的比我還像新娘闲延。我一直安慰自己,他們只是感情好韩玩,可當我...
    茶點故事閱讀 68,724評論 6 397
  • 文/花漫 我一把揭開白布垒玲。 她就那樣靜靜地躺著,像睡著了一般找颓。 火紅的嫁衣襯著肌膚如雪合愈。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,328評論 1 310
  • 那天击狮,我揣著相機與錄音佛析,去河邊找鬼。 笑死彪蓬,一個胖子當著我的面吹牛寸莫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播档冬,決...
    沈念sama閱讀 40,897評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼膘茎,長吁一口氣:“原來是場噩夢啊……” “哼桃纯!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起辽狈,我...
    開封第一講書人閱讀 39,804評論 0 276
  • 序言:老撾萬榮一對情侶失蹤慈参,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后刮萌,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體驮配,經(jīng)...
    沈念sama閱讀 46,345評論 1 318
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,431評論 3 340
  • 正文 我和宋清朗相戀三年着茸,在試婚紗的時候發(fā)現(xiàn)自己被綠了壮锻。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片历涝。...
    茶點故事閱讀 40,561評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡惊完,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出霹俺,到底是詐尸還是另有隱情敬特,我是刑警寧澤掰邢,帶...
    沈念sama閱讀 36,238評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站伟阔,受9級特大地震影響辣之,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜皱炉,卻給世界環(huán)境...
    茶點故事閱讀 41,928評論 3 334
  • 文/蒙蒙 一怀估、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧合搅,春花似錦多搀、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,417評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至赌髓,卻和暖如春从藤,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背春弥。 一陣腳步聲響...
    開封第一講書人閱讀 33,528評論 1 272
  • 我被黑心中介騙來泰國打工呛哟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人匿沛。 一個月前我還...
    沈念sama閱讀 48,983評論 3 376
  • 正文 我出身青樓扫责,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逃呼。 傳聞我的和親對象是個殘疾皇子鳖孤,可洞房花燭夜當晚...
    茶點故事閱讀 45,573評論 2 359

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