iOS架構(gòu)設(shè)計(jì):揭秘MVC, MVP, MVVM以及VIPER

不要錯(cuò)過最新的iOS開發(fā)技能樹 —— github地址

更新:在這里可以看到幻燈片
在iOS中使用MVC時(shí)感覺怪怪的今魔?對(duì)切換到MVVM有疑慮?聽說過VIPER,但不知道是否值得摔敛?

往下看,你將會(huì)找到這些問題的答案全封,如果還有疑問马昙,請(qǐng)?jiān)谠u(píng)論區(qū)留言桃犬。

你將了解到在iOS環(huán)境下如何進(jìn)行系統(tǒng)架構(gòu)設(shè)計(jì)。我們將簡單回顧一些流行的框架行楞,并通過實(shí)踐一些小例子來比較它們的理論攒暇。如果需要更多詳細(xì)信息,請(qǐng)參考文章中出現(xiàn)的鏈接子房。

掌握設(shè)計(jì)模式可能會(huì)讓人上癮形用,所以要小心:你可能在閱讀這篇文章之前已經(jīng)問過自己一些問題,比如說:
誰應(yīng)該擁有聯(lián)網(wǎng)請(qǐng)求:Model還是Controller证杭?
如何將Model傳遞到新View的View Model中田度?
誰創(chuàng)建了一個(gè)新的VIPER模塊:Router還是Presenter?

image

為什么要糾結(jié)選擇什么架構(gòu)呢解愤?

假如有一天镇饺,你在調(diào)試一個(gè)實(shí)現(xiàn)了幾十種功能的龐大的類時(shí),你會(huì)發(fā)現(xiàn)自己很難找到并修復(fù)你的類中的任何錯(cuò)誤送讲。并且奸笤,很難把這個(gè)類作為一個(gè)整體來考慮,因此哼鬓,你總會(huì)忽略一些重要的細(xì)節(jié)监右。如果你的應(yīng)用程序中已經(jīng)出現(xiàn)了這種情況,那么很有可能:

  • 這類是UIViewController類异希。
  • UIViewController直接存儲(chǔ)和處理你的數(shù)據(jù)
  • 你的UIView中幾乎沒有做任何事情
  • Model僅僅是一個(gè)數(shù)據(jù)結(jié)構(gòu)
  • 單元測(cè)試覆蓋不了任何內(nèi)容

即使你遵循了蘋果的指導(dǎo)方針并實(shí)現(xiàn)了蘋果的MVC模式健盒,這種情況還是會(huì)發(fā)生的,所以不要難過宠互。蘋果的MVC有點(diǎn)問題味榛,這個(gè)我們稍后再談。

讓我們定義一個(gè)優(yōu)秀系統(tǒng)結(jié)構(gòu)的特征:
1.角色間職責(zé)的清晰分配(分布式)予跌。
2.可測(cè)試性通常來自第一個(gè)特性(不必?fù)?dān)心:使用適當(dāng)?shù)南到y(tǒng)結(jié)構(gòu)是很容易的)。
3.使用方便善茎,維護(hù)成本低券册。

為什么要采用分布式

當(dāng)我們想弄清楚某些事情是如何運(yùn)作時(shí),采用分布式能讓我們的大腦思路清晰垂涯。如果你認(rèn)為你開發(fā)越多烁焙,你的大腦就越能理解復(fù)雜性,那么你是對(duì)的耕赘。但這種能力不是線性的骄蝇,很快就會(huì)達(dá)到上限。因此操骡,克服復(fù)雜性的最簡單方法是按照單一職責(zé)原則在多個(gè)實(shí)體之間劃分職責(zé)九火。

為什么要可測(cè)試

對(duì)于那些已經(jīng)習(xí)慣了單元測(cè)試的人來說赚窃,這通常不是問題,因?yàn)樵谔砑恿诵碌奶匦曰蛘咭黾右恍╊惖膹?fù)雜性之后通常會(huì)失敗岔激。這意味著測(cè)試能夠降低應(yīng)用程序在用戶的設(shè)備上發(fā)生問題的概率勒极,那時(shí)修復(fù)也許需要一個(gè)星期(審核)才能到達(dá)用戶。

為什么要易用性

這并不需要回答虑鼎,但值得一提的是辱匿,最好的代碼是從未編寫過的代碼。因此炫彩,你擁有的代碼越少匾七,你擁有的bug就越少。這意味著編寫更少代碼的愿望決不能僅僅由開發(fā)人員的懶惰來解釋江兢,你不應(yīng)該偏愛看起來更聰明的解決方案而忽視它的維護(hù)成本昨忆。

MV(X) 簡介

現(xiàn)在我們?cè)诩軜?gòu)設(shè)計(jì)模式上有很多選擇:

他們中的三個(gè)假設(shè)將應(yīng)用程序的實(shí)體分成3類:

  • Models — 負(fù)責(zé)保存數(shù)據(jù)或數(shù)據(jù)訪問層,操縱數(shù)據(jù)划址,例如“人”或“提供數(shù)據(jù)的人”扔嵌。
  • Views? — ?負(fù)責(zé)表示層(GUI),iOS環(huán)境下通常以“UI”前綴夺颤。
  • Controller/Presenter/ViewModel? — ?Model和View之間的中介痢缎,一般負(fù)責(zé)在用戶操作View時(shí)更新Model,以及當(dāng)Model變化時(shí)更新View世澜。

這種劃分能讓我們:

  • 更好地理解它們(如我們所知)
  • 重用它們(尤其是View和Model)
  • 獨(dú)立地進(jìn)行測(cè)試(單元測(cè)試)

讓我們從MV(X)開始独旷,稍后在回到VIPER:

MVC

曾經(jīng)

在討論蘋果對(duì)MVC的看法之前,讓我們先看看傳統(tǒng)的MVC寥裂。

image

在上圖的情況下嵌洼,View是無狀態(tài)的。一旦Model被改變封恰,Controller就會(huì)簡單地渲染它麻养。例如:網(wǎng)頁完全加載后,一旦你按下鏈接诺舔,就導(dǎo)航到其他地方鳖昌。
雖然在iOS應(yīng)用用傳統(tǒng)的MVC架構(gòu)也可以實(shí)現(xiàn),但這并沒有多大意義低飒,由于架構(gòu)問題?——三個(gè)實(shí)體是緊耦合的许昨,每個(gè)實(shí)體和其他兩個(gè)通信。這大大降低了可重用性——這可不是你希望在你的應(yīng)用程序看到的褥赊。出于這個(gè)原因糕档,我們甚至不想編寫規(guī)范的MVC示例。

傳統(tǒng)的MVC似乎不適用于現(xiàn)代IOS開發(fā)拌喉。

蘋果的MVC

愿景:
image

Controller是View和Model之間的中介速那,這樣他們就解耦了俐银。最小的可重用單元是Controller,這對(duì)我們來說是個(gè)好消息琅坡,因?yàn)槲覀儽仨氂幸粋€(gè)來放那些不適合放入Model的復(fù)雜業(yè)務(wù)邏輯的地方悉患。
從理論上講,它看起來很簡單榆俺,但你覺得有些地方不對(duì)售躁,對(duì)吧?你甚至聽到有人說MVC全稱應(yīng)該改為Massive View Controller(大量的視圖控制器)茴晋。此外陪捷,為View controller減負(fù)也成為iOS開發(fā)者面臨的一個(gè)重要話題。
如果蘋果只接受傳統(tǒng)的MVC并改進(jìn)了它诺擅,為什么會(huì)出現(xiàn)這種情況呢市袖?

實(shí)際情況:
image

Cocoa MVC鼓勵(lì)人們編寫大規(guī)模的視圖控制器,而且由于它們涉及View的生命周期烁涌,所以很難說它們(View和Controller)是分離的苍碟。
雖然你仍有能力將一些業(yè)務(wù)邏輯和數(shù)據(jù)轉(zhuǎn)換成Model,但你沒辦法將View從Controller中分離撮执。在大多數(shù)時(shí)候所有View的責(zé)任是把事件傳遞給Controller微峰。
ViewController最終演變成一個(gè)其他人的delegate和data source,通常負(fù)責(zé)分派和取消網(wǎng)絡(luò)請(qǐng)求…你明白的抒钱。
你見過多少這樣的代碼蜓肆?:

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

Cell(一個(gè)View)跟一個(gè)Model直接綁定了!所以MVC準(zhǔn)則被違反了谋币,但是這種情況總是發(fā)生仗扬,通常人們不會(huì)覺得它是錯(cuò)誤的。如果你嚴(yán)格遵循MVC蕾额,那么你應(yīng)該從Controller配置cell早芭,而不是將Model傳遞到cell中,這將增大Controller诅蝶。

Cocoa MVC 的全稱應(yīng)該是 Massive View Controller.

在單元測(cè)試之前逼友,這個(gè)問題可能并不明顯(希望在你的項(xiàng)目中是這樣)。
由于視圖控制器與視圖緊密耦合秤涩,因此很難測(cè)試——因?yàn)樵诰帉懸晥D控制器的代碼時(shí),你必須模擬View的生命周期司抱,從而使你的業(yè)務(wù)邏輯盡可能地與View層的代碼分隔開來筐眷。
讓我們看一看簡單的操場例子:

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

    }
    // 這里寫布局代碼
}
// 組裝MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;

MVC在可見的ViewController中進(jìn)行組裝

這似乎不太容易測(cè)試,對(duì)嗎习柠?
我們可以將greeting移動(dòng)到新的GreetingModel類中并分別進(jìn)行測(cè)試匀谣,但我們不能在不調(diào)用GreetingViewController的有關(guān)方法(viewDidLoad, didTapButton照棋,這將會(huì)加載所有的View) 的情況下測(cè)試UIView中的顯示邏輯(雖然在上面的例子中沒有太多這樣的邏輯)。這不利于單元測(cè)試武翎。
事實(shí)上烈炭,在一個(gè)模擬器(如iPhone 4S)中測(cè)試UIViews并不能保證它會(huì)在其他設(shè)備良好的工作(例如iPad),所以我建議從你的單元測(cè)試Target中刪除“Host Application”選項(xiàng)宝恶,然后脫離應(yīng)用程序運(yùn)行你的測(cè)試符隙。

View和Controller之間的交互在單元測(cè)試中是不可測(cè)試的。

如此看來垫毙,Cocoa MVC 模式 似乎是一個(gè)很糟糕的選擇霹疫。但是讓我們根據(jù)文章開頭定義的特性來評(píng)估它:

  • 職責(zé)拆分 — View和Model實(shí)現(xiàn)了分離,但是View與Controller仍是緊耦合综芥。
  • 可測(cè)性 — 由于模式的原因丽蝎,你只能測(cè)試你的Model。
  • 易用性 — 相比于其他模式代碼量最少膀藐。此外屠阻,每個(gè)人都熟悉它,即使經(jīng)驗(yàn)不太豐富的開發(fā)人員也能夠維護(hù)它额各。

如果你不愿意在項(xiàng)目的架構(gòu)上投入太多的時(shí)間国觉,那么Cocoa MVC 就是你應(yīng)該選擇的模式。而且你會(huì)發(fā)現(xiàn)用其他維護(hù)成本較高的模式開發(fā)小的應(yīng)用是一個(gè)致命的錯(cuò)誤臊泰。

Cocoa MVC是開發(fā)速度最快的架構(gòu)模式蛉加。

MVP

MVP 實(shí)現(xiàn)了Cocoa的MVC的愿景
image

這看起來不正是蘋果的MVC嗎?是的缸逃,它的名字是MVP(Passive View variant针饥,被動(dòng)視圖變體)。等等...這是不是意味著蘋果的MVC實(shí)際上是MVP需频?不丁眼,不是這樣。如果你仔細(xì)回憶一下昭殉,View是和Controller緊密耦合的苞七,但是MVP的中介Presenter并沒有對(duì)ViewController的生命周期做任何改變,因此View可以很容易的被模擬出來挪丢。在Presenter中根本沒有和布局有關(guān)的代碼蹂风,但是它卻負(fù)責(zé)更新View的數(shù)據(jù)和狀態(tài)。

image

假如告訴你,UIViewController就是View呢乾蓬?

在MVP中惠啄,UIViewController的子類實(shí)際上是Views而不是Presenters。這種模式的可測(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 {
    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
    }

    // 布局代碼
}
// 裝配 MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter

裝配問題的重要說明

MVP是第一個(gè)揭示裝配問題的模式,因?yàn)樗腥齻€(gè)獨(dú)立的層趋距。既然我們不希望View和Model耦合粒氧,那么在顯示的View Controller(其實(shí)就是View)中處理這種協(xié)調(diào)的邏輯就是不正確的,因此我們需要在其他地方來做這些事情节腐。例如外盯,我們可以做基于整個(gè)App范圍內(nèi)的路由服務(wù),由它來負(fù)責(zé)執(zhí)行協(xié)調(diào)任務(wù)铜跑,以及View到View的展示门怪。這不僅僅是在MVP模式中必須處理的問題,同時(shí)也存在于以下集中方案中锅纺。

讓我們看看MVP的特點(diǎn):

  • 職責(zé)拆分 — 我們將最主要的任務(wù)劃分到Presenter和Model掷空,而View的功能較少(雖然上述例子中Model的任務(wù)也并不多)。
  • 可測(cè)性 — 非常好囤锉,基于一個(gè)功能簡單的View層坦弟,可以測(cè)試大多數(shù)業(yè)務(wù)邏輯
  • 易用性 — 在我們上邊不切實(shí)際的簡單的例子中,代碼量是MVC模式的2倍官地,但同時(shí)MVP的概念卻非常清晰酿傍。

iOS 中的MVP意味著可測(cè)試性強(qiáng)、代碼量大驱入。

MVP

關(guān)于Bindings和Hooters

還有一些其他形態(tài)的MVP —— Supervising Controller MVP(監(jiān)聽Controller的MVP)赤炒。這個(gè)變體的變化包括View和Model之間的直接綁定,但是Presenter(Supervising Controller)仍然來管理來自View的動(dòng)作事件亏较,同時(shí)也能勝任對(duì)View的更新莺褒。

image

但是我們之前就了解到,模糊的職責(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)的問題誊锭。
從理論層面來講Model-View-ViewModel看起來不錯(cuò),我們已經(jīng)非常熟悉View和Model弥锄,以及Meditor(中介)炉旷,在這里它叫做View Model签孔。

image

它和MVP模式看起來很像:

  • MVVM也將ViewController視作View
  • 在View和Model之間沒有耦合

此外,它還有像Supervising版本的MVP那樣的綁定功能窘行,但這個(gè)綁定不是在View和Model之間而是在View和ViewModel之間。

那么在iOS中ViewModel到底代表了什么图仓?它基本上就是UIKit下的獨(dú)立控件以及控件的狀態(tài)罐盔。ViewModel調(diào)用會(huì)改變Model同時(shí)會(huì)將Model的改變更新到自身并且因?yàn)槲覀兘壎薞iew和ViewModel,第一步就是相應(yīng)的更新狀態(tài)救崔。

綁定

我在MVP部分已經(jīng)提到這點(diǎn)了惶看,但是在這里我們來繼續(xù)討論。
綁定是從OS X開發(fā)中衍生出來的六孵,但是我們沒有在iOS開發(fā)中使用它們纬黎。當(dāng)然我們有KVO通知,但它們沒有綁定方便劫窒。
如果我們自己不想自己實(shí)現(xiàn)本今,那么我們有兩種選擇:

事實(shí)上,尤其是最近孕索,你聽到MVVM就會(huì)想到ReactiveCoca逛艰,反之亦然。盡管通過簡單的綁定來使用MVVM是可實(shí)現(xiàn)的搞旭,但是ReactiveCocoa(或其變體)卻能更好的發(fā)揮MVVM的特點(diǎn)散怖。

函數(shù)響應(yīng)式框架有一個(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)用棧:

image

在我們簡單的例子中偏灿,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 {
    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
}
// 裝配 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)估:

  • 職責(zé)拆分 — 在例子中并不是很清晰,但是事實(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架構(gòu)經(jīng)驗(yàn)遷移到iOS app的設(shè)計(jì)

VIPER是我們最后要介紹的民效,由于不是來自于MV(X)系列,它具備一定的趣味性侍芝。

到目前為止研铆,你必須同意劃分責(zé)任的粒度是很好的選擇。VIPER在責(zé)任劃分層面進(jìn)行了迭代州叠,VIPER分為五個(gè)層次:

image
  • 交互器(Interactor) — 包括關(guān)于數(shù)據(jù)和網(wǎng)絡(luò)請(qǐng)求的業(yè)務(wù)邏輯棵红,例如創(chuàng)建一個(gè)實(shí)體(Entities),或者從服務(wù)器中獲取一些數(shù)據(jù)咧栗。為了實(shí)現(xiàn)這些功能逆甜,需要使用服務(wù)、管理器致板,但是他們并不被認(rèn)為是VIPER架構(gòu)內(nèi)的模塊交煞,而是外部依賴。
  • 展示器(Presenter) — 包含UI層面(但UIKit獨(dú)立)的業(yè)務(wù)邏輯以及在交互器(Interactor)層面的方法調(diào)用斟或。
  • 實(shí)體(Entities) — 普通的數(shù)據(jù)對(duì)象素征,不屬于數(shù)據(jù)訪問層,因?yàn)閿?shù)據(jù)訪問屬于交互器(Interactor)的職責(zé)萝挤。
  • 路由器(Router) — 用來連接VIPER的各個(gè)模塊御毅。

基本上,VIPER的模塊可以是一個(gè)屏幕或者用戶使用應(yīng)用的整個(gè)過程 —— 例如認(rèn)證過程怜珍,可以由一屏完成或者需要幾步才能完成端蛆。你想讓模塊多大,這取決于你酥泛。

當(dāng)我們把VIPER和MV(X)系列作比較時(shí)今豆,我們會(huì)在職責(zé)劃分方面發(fā)現(xiàn)一些不同:

  • Model(數(shù)據(jù)交互)邏輯以實(shí)體(Entities)為單位拆分到交互器(Interactor)中嫌拣。
  • Controller/Presenter/ViewModel 的UI展示方面的職責(zé)移到了Presenter中,但是并沒有數(shù)據(jù)轉(zhuǎn)換相關(guān)的操作呆躲。
  • VIPER 是第一個(gè)通過路由器(Router)實(shí)現(xiàn)明確的地址導(dǎo)航的模式异逐。

找到一個(gè)適合的方法來實(shí)現(xiàn)路由對(duì)于iOS應(yīng)用是一個(gè)挑戰(zhàn),MV(X)系列并未涉及這一問題歼秽。

例子中并不包含路由和模塊之間的交互应役,所以和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
    }

    // 布局代碼
}
// 裝配 VIPER 模塊(不包含路由)
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)估一下特性:

  • 職責(zé)拆分 — 毫無疑問燥筷,VIPER是任務(wù)劃分中的佼佼者。
  • 可測(cè)性 — 不出意外地院崇,更好的分布性就有更好的可測(cè)試性肆氓。
  • 易用性 — 最后你可能已經(jīng)猜到了維護(hù)成本方面的問題。你必須為很小功能的類寫出大量的接口底瓣。
什么是LEGO

當(dāng)使用VIPER時(shí)谢揪,你可能想像用樂高積木來搭建一個(gè)城堡,這個(gè)想法可能存在一些問題捐凭。也許拨扶,現(xiàn)在就應(yīng)用VIPER架構(gòu)還為時(shí)過早,考慮一些更為簡單的模式反而會(huì)更好茁肠。一些人會(huì)忽略這些問題患民,大材小用。假定他們篤信VIPER架構(gòu)會(huì)在未來給他們的應(yīng)用帶來一些好處垦梆,雖然現(xiàn)在維護(hù)起來確實(shí)是有些費(fèi)勁匹颤。如果你也持這樣的觀點(diǎn),我為你推薦 Generamba 這個(gè)用來搭建VIPER架構(gòu)的工具托猩。雖然我個(gè)人感覺這是在用高射炮打蚊子印蓖。

總結(jié)

我們研究了幾種架構(gòu)模式,希望你能找到一些困擾你的問題的答案京腥。但毫無疑問通過閱讀這篇文章你應(yīng)該已經(jīng)認(rèn)識(shí)到了沒有絕對(duì)的解決方案赦肃。所以架構(gòu)模式的選擇需要根據(jù)實(shí)際情況進(jìn)行利弊分析。
因此公浪,在同一應(yīng)用程序中混合架構(gòu)是很自然的他宛。例如:你開始的時(shí)候使用MVC,然后突然意識(shí)到一個(gè)頁面在MVC模式下的變得越來越難以維護(hù)因悲,然后就切換到MVVM架構(gòu)堕汞,但是僅僅針對(duì)這一個(gè)頁面。并沒有必要對(duì)哪些MVC模式下運(yùn)轉(zhuǎn)良好的頁面進(jìn)行重構(gòu)晃琳,因?yàn)槎呤强梢圆⒋娴摹?/p>

讓一切盡可能簡單讯检,但不是愚蠢琐鲁。 ?—— ?阿爾伯特·愛因斯坦

原文鏈接:http://www.reibang.com/p/68aa6071c02e
關(guān)注微信公眾號(hào):iOSSir,每日更新蘋果資訊人灼、技術(shù)干貨围段!

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市投放,隨后出現(xiàn)的幾起案子奈泪,更是在濱河造成了極大的恐慌,老刑警劉巖灸芳,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件涝桅,死亡現(xiàn)場離奇詭異,居然都是意外死亡烙样,警方通過查閱死者的電腦和手機(jī)冯遂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來谒获,“玉大人蛤肌,你說我怎么就攤上這事∨” “怎么了裸准?”我有些...
    開封第一講書人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵,是天一觀的道長赔硫。 經(jīng)常有香客問我炒俱,道長,這世上最難降的妖魔是什么卦停? 我笑而不...
    開封第一講書人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任向胡,我火速辦了婚禮,結(jié)果婚禮上惊完,老公的妹妹穿的比我還像新娘僵芹。我一直安慰自己,他們只是感情好小槐,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開白布拇派。 她就那樣靜靜地躺著,像睡著了一般凿跳。 火紅的嫁衣襯著肌膚如雪件豌。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,784評(píng)論 1 290
  • 那天控嗜,我揣著相機(jī)與錄音茧彤,去河邊找鬼。 笑死疆栏,一個(gè)胖子當(dāng)著我的面吹牛曾掂,可吹牛的內(nèi)容都是我干的惫谤。 我是一名探鬼主播,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼珠洗,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼溜歪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起许蓖,我...
    開封第一講書人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤蝴猪,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后膊爪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體自阱,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年米酬,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了动壤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡淮逻,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出阁簸,到底是詐尸還是另有隱情爬早,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布启妹,位于F島的核電站筛严,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏饶米。R本人自食惡果不足惜桨啃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望檬输。 院中可真熱鬧照瘾,春花似錦、人聲如沸丧慈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽逃默。三九已至鹃愤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間完域,已是汗流浹背软吐。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留吟税,地道東北人凹耙。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓姿现,卻偏偏與公主長得像,于是被迫代替她去往敵國和親使兔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子建钥,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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