【原文】https://medium.com/ios-os-x-development/ios-architecture-patterns-ecba4c38de52
使用MVC
進行iOS
開發(fā)感覺到很怪異堪唐?在切換到MVVM
的時候心存疑慮?聽說過VIPER
该贾,但是不知道是否值得采用羔杨?
讀下去,這篇文章將為你一一解惑杨蛋。
如果你正打算組織一下在iOS
環(huán)境下你掌握的架構(gòu)模式知識體系兜材。我們接下來回簡單地回顧幾個流行的架構(gòu)并做幾個小的練習(xí)。關(guān)于某個例子如果你想了解的更詳細(xì)一些逞力,可以查看下方的鏈接曙寡。
掌握設(shè)計模式會讓人沉迷其中,所有一定要當(dāng)心:相比閱讀本文章之前寇荧,你可能會問更多像這樣的問題:
由誰進行網(wǎng)絡(luò)請求:Model
举庶?還是ViewController
?**
如何向新視圖(View
)的ViewModel
中傳遞Model
**
由誰創(chuàng)建一個新的VIPER
模塊:路由(Router
)?還是展示器(Presenter
)揩抡?**
為什么在乎架構(gòu)的選擇户侥?
因為如果你不這樣做,終有一天峦嗤,你在調(diào)試一個擁有著數(shù)十個不同方法和變量(things)的龐大的類文件時蕊唐,你會發(fā)現(xiàn)你無法找到并修復(fù)此文件中的任何問題。自然地烁设,也很難把這個類文件當(dāng)做一個整體而熟稔于心替梨,這樣你可能總是會錯過一些重要的細(xì)節(jié)。如果你的應(yīng)用已經(jīng)處于這樣的境況,很有可能是這樣:
這個類是
UIViewController
的子類副瀑。你的數(shù)據(jù)直接存儲在
UIViewController
中弓熏。你的
UIViews
什么都不做。Model
是啞數(shù)據(jù)結(jié)構(gòu)(dumb data structure
)糠睡。
dumb data structure: 只用來存儲數(shù)據(jù)的結(jié)構(gòu)挽鞠,沒有任何方法。詳見:https://stackoverflow.com/questions/32944751/what-is-dumb-data-and-dumb-data-object-mean
- 單元測試沒有0覆蓋狈孔。
即使你是按照蘋果的指導(dǎo)方針并實現(xiàn)蘋果的MVC模式滞谢,也會出現(xiàn)上述問題,所有不要難過除抛。蘋果的MVC模式存在著一些些問題,這點我們稍后再說母截。
讓我們定義一下一個好的架構(gòu)應(yīng)該有的********特點********:
能把代碼職責(zé)均衡的解耦到不同的功能類里到忽。(Balanced distribution of responsibilities among entities with strict roles.)
可測試性(Testability usually comes from the first feature.)。
易用清寇、維護成本低(Ease of use and a low maintenance cost.)喘漏。
解耦( Why Distribution ?)
在我們試弄清楚事物是如何運作的時候,解耦可以保證大腦的負(fù)載均衡华烟。如果你認(rèn)為開發(fā)的(項目)越多你的大腦越能適應(yīng)理解復(fù)雜的問題翩迈,那么你就是對的。但是這個能力不是線性擴展的并且很快就能達到上限盔夜。所以负饲,解決復(fù)雜性的最簡單的方式就是在多個實體間按照“單一責(zé)任原則” 拆分職責(zé)。
可測試(Why Testability ?)
對于那些已經(jīng)習(xí)慣了單元測試的人來說這并不是一個問題喂链,因為再添加了新的特性和重構(gòu)了一個復(fù)雜的類后通常會運行失敗返十。這意味著單元測試可以幫助開發(fā)者發(fā)現(xiàn)一些在運行時才會出現(xiàn)的問題,并且這些問題常見于安裝在用戶的手機上的應(yīng)用上椭微,此外要修復(fù)這些問題也需要大概一周的時間洞坑。
易用(Why Ease of use ?)
這個問題并不需要回答,但蝇率,值得一提的是:最好的代碼總是那些沒有被寫出來的代碼迟杂。因此,代碼越少本慕,錯誤也就越少排拷。這也說明,總想著寫最少代碼的開發(fā)者不是因為他們懶间狂,并且你也不應(yīng)該因為一個更聰明的解決方案而忽視維護成本攻泼。
MV(X)概要
現(xiàn)今,當(dāng)我們提及架構(gòu)設(shè)計模式的時候,我們有很多的選擇忙菠。比如:
MVC
MVP
MVVM
VIPER
上述的前三個架構(gòu)采取的是何鸡,把應(yīng)用中的實體(entities)放入下面三個類別中其中一個內(nèi)。
Models
——負(fù)責(zé)域數(shù)據(jù)(domain data)和操作數(shù)據(jù)的數(shù)據(jù)訪問層(Data access layer)牛欢,可認(rèn)為"Person"和"PersonDataProvider"類骡男。Views
——負(fù)責(zé)表現(xiàn)層(GUI),對于iOS環(huán)境來說就是所有以"UI"開頭的類傍睹。Controller
/Presenter
/ViewModel
——模型(Model
)和視圖(View
)的粘合劑隔盛、中介,通常的負(fù)責(zé)通過響應(yīng)用戶在視圖(View
)上的操作通知模型(Model
)更新拾稳、并通過模型的改變來更新視圖(View
)吮炕。
實體解耦能讓我們:
更好的理解它們(這點我么已經(jīng)知道)
復(fù)用(大多用于視圖(
View
)和模型(Model
))獨立測試
讓我們想看一下MV(X)
架構(gòu),之后再回過頭來看VIPER
MVC
MVC前世
在討論蘋果的MVC架構(gòu)時访得,先來看一下傳統(tǒng)的MVC是什么樣的龙亲。
在這種情況中,視圖(View
)是無狀態(tài)的悍抑。一旦模型(Model
)改變視圖(View
)僅僅只是被控制器(Controller
)渲染而已鳄炉。想象一下點擊一個鏈接導(dǎo)航到其他地方后網(wǎng)頁完全加載出來的情況。盡管搜骡,iOS應(yīng)用可以使用傳統(tǒng)的MVC架構(gòu)拂盯,但這并沒有多大意義,因為架構(gòu)本身就存在問題——三個實體(entities
)之間聯(lián)系太過緊密记靡,每一個實體都知道(引用)另外兩個實體谈竿。這就導(dǎo)致了實體的復(fù)用性急劇下降——在你的應(yīng)用中這并不是你所想要的。出于這個原因摸吠,我們就不寫MVC范例了榕订。
Traditional MVC doesn't seems to be applicable to modern iOS development.
傳統(tǒng)MVC架構(gòu)看上去并不適合用于現(xiàn)在的iOS開發(fā)中。
Apple's MVC
預(yù)期
控制器(Controller
)是視圖(View
)與模型(Model
)兩者之間的中介蜕便,這使得視圖(View
)與模型(Model
)都不知道對方的存在劫恒。控制器(Controller
)是可復(fù)用的最少的轿腺,對我們來說這通常很好两嘴,因為我們必須有一個地方去放置一些不適合放在模型(Model
)中且比較棘手的邏輯。
理論上族壳,看起來非常簡單憔辫,但是你感覺到有些地方不對,是不是這樣仿荆?你甚至聽說過人們把MVC稱作Massive View Controller贰您。此外坏平,視圖控制器"瘦身"(View Controller offloading)成了iOS開發(fā)者中的一個重要話題。如果蘋果只是采用傳統(tǒng)MVC
架構(gòu)或者只是稍加改進锦亦,為什么會出現(xiàn)這種情況舶替?
MVC今生(現(xiàn)實情況)
Cocoa MVC
鼓勵你使用大型的視圖控制器(****Massive**** View Controllers)凄硼,由于他們都參與到了視圖(View
)的生命周期中了以至于很難說他們是分離的叹哭。盡管你仍有能力分流一些業(yè)務(wù)邏輯和數(shù)據(jù)轉(zhuǎn)換功能到模型(Model
)中瘾带,但是當(dāng)涉及到把工作分流到視圖(View
)中去時你就誒有更多的選擇了你踩,因為在大多數(shù)時候視圖(View
)的所有職責(zé)是把動作傳遞到控制器(Controller
)中。視圖控制器(View Controller
)最終最為所有控件的委托和數(shù)據(jù)源舅踪,通常負(fù)責(zé)調(diào)度和取消網(wǎng)絡(luò)請求...應(yīng)有盡有
你見過多少次這樣的代碼:
var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
userCell.configureWithUser(user)
cell
這個視圖是由Model直接配置數(shù)據(jù)的抚岗,因此這違反了MVC
指南雇庙,但是這種情況無時無刻不在發(fā)生著瞧甩,而且通常人們并不認(rèn)為這樣有什么錯的钉跷。如果你嚴(yán)格的遵守MVC
架構(gòu),那么你應(yīng)該在Controller
中配置cell數(shù)據(jù)肚逸,不用把Model
傳遞到View
中去尘应,這會增加控制器的大小(復(fù)雜度)吼虎。
Cocoa
MVC
is reasonably unabbreviated the Massive View Controller.
Cocoa
MVC
被稱作大型視圖控制器是合理的。
在未提及單元測試(Unit Testing)前MVC
的問題并不是很明顯(希望苍鲜,你的項目中有單元測試)思灰。由于你的視圖控制器(View Controller
)與視圖(View
)是緊耦合的,因此很難對其進行測試混滔,因為你不得不非常有創(chuàng)造性的模擬視圖和他們的生命周期洒疚,使用這種方式編寫視圖控制器(View Controller
)代碼,你要盡可能的把業(yè)務(wù)邏輯和視圖布局代碼分離開來坯屿。
讓我們來看一個簡單地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:", forControlEvent: .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 assembling can be performed in the presenting view controller
組合
MVC
可以在展示視圖控制器(presenting view controller
)中來完成
不是很容易測試油湖,是不是這樣?我們可以把生成greeting
的代碼放入到GreetingModel
類里并單獨的進行測試领跛,但是乏德,在沒有直接調(diào)用與UIView
有關(guān)的方法(如:viewDidLoad
, didTapButton
,這些方法可能會加載所有的視圖吠昭,不利于單元測試喊括。)的情況下,我們無法測試GreetingViewController
中的任何展示邏輯(盡管上面的代碼中沒有太多這樣的邏輯)矢棚。
事實上郑什,在模擬器(如:iPhone4s
)上加載并測試視圖并不能保證在其他設(shè)備(如:iPad
)上也能正常工作,所以蒲肋,我建議從Unit Test目標(biāo)(Unit Test target
)配置中移除主應(yīng)用程序(Host Application
)并在模擬器上沒有應(yīng)用運行的情況下運行測試蘑拯。
The interactions between the View and the Controller aren't really testable with Unit Tests.
視圖和控制器之間的交互很難進行單元測試钝满。
綜上所述,Cocoa MVC
可能是一個相當(dāng)糟糕的選擇申窘。讓我們按照文章開頭定義的特點來評估一下這種架構(gòu)模式:
解耦(Distribution)——視圖(
View
)和模型(Model
)確實解耦了弯蚜,然而,視圖(View
)和控制器(Controller
)卻是緊密耦合的偶洋。可測試(Testability)——由于緊耦合的關(guān)系熟吏,你只能測試視圖(
Model
)。易用(Ease of use)——同其他模式相比代碼最少玄窝。此外牵寺,大家都熟悉它,因此恩脂,很用以掌握甚至是新手帽氓。
如果你沒有打算在架構(gòu)時耗費太多時間并且覺得高成本的維護費用對你的小項目來說是一種過度的浪費的話,那么Cocoa MVC
就是你的最好選擇俩块。
Cocoa MVC is the best architectural pattern in term of the speed of the development.
在開發(fā)速度上面
Cocoa MVC
是最好的架構(gòu)模式黎休。
MVP
Cocoa MVC’s promises delivered
是不是很像蘋果的MVC架構(gòu)?沒錯玉凯,確實如此势腮,它就是MVP
(Passive View variant)。等下...是不是Apple’s MVC
事實上就是MVP
漫仆?并不是捎拯,回想一下在MVC
中View
和Controller
是緊密耦合的,然而盲厌,MVP的中介——Presenter
與View Controller的生命周期沒有任何關(guān)系署照,并且很容易模擬View,所以Presenter
中沒有任何布局代碼吗浩,但是它卻負(fù)責(zé)用數(shù)據(jù)和狀態(tài)更新`Vie
What if i told you建芙,the UIViewController is the 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: GreetingViePresenter!
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 go here
}
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenter
關(guān)于組裝的重要提示
MVP
是第一個揭示出組裝問題(assembly problem)的架構(gòu)模式炕倘,而出現(xiàn)這個問題的原因是它有三個實際上獨立的層钧大。由于我們不想讓視圖(View
)了解模型(Model
),所以在展示視圖控制器(也就是視圖)執(zhí)行組裝是不正確的罩旋,因此我們不得不在其他地方執(zhí)行它啊央。例如眶诈,我們可以創(chuàng)建一個app范圍(app-wide)的路由(Router
)服務(wù),讓它來完成執(zhí)行組裝和視圖到視圖(View-to-View
)的展示功能瓜饥。這個問題不止在MVP
中存在逝撬,在下面介紹的其他模式中也存在。
讓我們看一下MVP
的特點:
解耦(Distribution)——我們在最大程度上分離了展示器(
Presenter
)和模型(Model
)乓土,還有相當(dāng)簡單宪潮、輕薄的視圖(dumb view
)(在上述例子中的模型也很簡單)。可測試性(Testability)——很棒趣苏,由于簡單的視圖狡相,我們可以測試大多數(shù)的業(yè)務(wù)邏輯。
易用性(Easy of use)——在我們簡單不完整的例子中食磕,相比于MVC這些代碼成倍的增加了尽棕,但是與此同時,MVP模式的思路卻更加的清晰彬伦。
MVP in iOS means superb testability and a lot of code
iOS
中的MVP
架構(gòu)意味著極好的可測試性和大量的代碼滔悉。
綁定和Hooters
還有一種類型的MVP
架構(gòu)模式——the Supervising Controller MVP。這個變種包括了視圖和模型的直接綁定单绑,展示器(The Supervising Controller)在處理動作的同時還可以改變視圖回官。
但是,就如我們已經(jīng)知道的搂橙,模糊的職責(zé)拆分是不正確的歉提,視圖和模型的緊耦合也同樣不可取。這和Cocoa
桌面應(yīng)用開發(fā)很相似份氧。和傳統(tǒng)的MVC
一樣,給有瑕疵的架構(gòu)寫例子沒有任何意義弯屈。
MVVM
MV(X)
類中近期最優(yōu)秀的架構(gòu)(The latest and the greatest of the MV(X) kind)
MVVM是MV(X)這類中最新的架構(gòu)形式蜗帜,所以,我們希望它能夠解決MV(X)
之前所面臨的問題资厉。
理論上厅缺,Model-View_ViewModel
這種架構(gòu)很棒。不僅View
和Model
宴偿,而且Mediator
——相當(dāng)于View Model
湘捎,我們都已經(jīng)熟悉。
它和MVP
很相似:
MVVM把視圖控制器當(dāng)做視圖窄刘。
視圖(
View
)和模型(Model
)之間不存在緊耦合窥妇。
另外,它還可以像MVP
那樣綁定娩践;但是綁定不是發(fā)生在視圖(View
)和模型(Model
)之間活翩,而是視圖(View
)和視圖模型(View Model
)之間烹骨。
那么,iOS現(xiàn)實中的視圖模型(View Model
)的廬山面目是什么材泄?它是你的視圖及其狀態(tài)的基本的UIKit
的獨立展示沮焕。視圖模型觸發(fā)模型的改變,并利用改變后的Model
更新自己拉宗,由于我們在視圖和視圖模型之間進行了綁定峦树,視圖也會根據(jù)視圖模型的改變而改變。
綁定(Bindings)
綁定我在講解MVP
架構(gòu)部分簡單的提到過旦事,這里我們在對其進行一些討論魁巩。綁定是從OSX
開發(fā)而來的,而且iOS中并沒有這個概念族檬。當(dāng)然歪赢,我們有KVO
和通知(notifications
),但是它的使用并沒有綁定方便单料。
所以埋凯,倘若不想自己編寫綁定代碼,我們還有兩個選擇:
一個是扫尖,基于KVO的綁定庫白对,像RZDataBinding或者SwiftBond。
完全的函數(shù)式響應(yīng)式編程野獸换怖,像ReactiveCocoa甩恼、RxSwift或者PromiseKit`。
事實上沉颂,現(xiàn)今条摸,只要你聽到“MVVM
”你就會想到ReactiveCocoa
,反之亦然铸屉。盡管使用簡單地綁定也可以創(chuàng)建MVVM
架構(gòu)的項目钉蒲,但是,ReactiveCocoa
(或者同類的庫)卻可以讓你把使用MVVM
架構(gòu)的優(yōu)勢最大化彻坛。
關(guān)于Reactive
庫有一個殘酷的現(xiàn)實需要面對:功能強大卻伴隨著巨大的職責(zé)顷啼。當(dāng)使用Reactive
庫的時候極容易把很多事情搞混,如果出現(xiàn)錯誤昌屉,你可能需要花費很多的時間去在APP中定位問題所在钙蒙,所以看一下下圖的調(diào)用堆棧。
在簡單的例子中间驮,使用FRF(functional reactive function:函數(shù)式響應(yīng)式函數(shù))庫甚至KVO都顯得大材小用躬厌,相反我們顯式使用showGreeting
方法讓視圖模型(View Model
)更新,并使用greetingDidChange
回調(diào)函數(shù)這樣一個簡單地屬性竞帽。
import UIKit
struct Person { //Model
let firstName: String
let lastName: String
}
protocol GreetingViewModelProtocol: class {
var greeting: String? {get}
// function to call when greeting did change
var greetingDidChange: ((GreetingViewModelProtocol) -> ()) ? (get set)
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
}
let model = Person(firstName: "David", lastName: "Blaine")
let viewModel = GreetingViewModel(person: model)
let view = GreetingViewController()
view.viewModel = viewModel
再回過來看一下我們的特點評估:
解耦(Distribution)——在我們上面的的簡例中可能不太明顯烤咧,事實上偏陪,MVVM的視圖比
MVP
的視圖擁有更多的職責(zé)。因為煮嫌,前者通過綁定從視圖模型(ViewModel
)更新自己笛谦,而后者則是把所有的事件前置到Presenter
中,也不對自己的狀態(tài)進行更新昌阿。可測試性(Testability)——
View Model
對View
一無所知饥脑,這可以讓我們輕易地對其進行測試。也可以對視圖(View
)測試懦冰,但由于UIKit
依賴灶轰,你可能想跳過她。易用性(Easy of use)——在我們的例子中刷钢,
MVVM
同MVP
有同樣的代碼量笋颤,但是在實際的應(yīng)用中,對于MVP
你需要把所有事件從視圖(View
)前置到展示器(Presenter
)并手動的更新視圖内地,而對于MVVM
伴澄,如果你使用了綁定則會變的很容易。
MVVM極其吸人眼球阱缓,它融合了上述所有架構(gòu)的的優(yōu)勢非凌,此外,由于它在視圖(
View
)端進行了綁定荆针,你可以不需要任何額外的代碼對視圖(View
)進行更新敞嗡。雖然如此,可測試性依然保持在一個很好的層次航背。
VIPER
把搭建樂高積木的經(jīng)驗?zāi)玫絠OS應(yīng)用設(shè)計中使用
VIPER
使我們最后的選擇喉悴,這種架構(gòu)尤為有趣,因為他不是屬于MV(X)類的架構(gòu)玖媚。
到目前為止箕肃,關(guān)于職責(zé)粒度的劃分非常合理這點你肯定贊同。VIPER
在職責(zé)劃分上面又做了一次迭代最盅,這次我們一共有五層突雪。
交互器(Interactor)——包含與數(shù)據(jù)(Entities)或者網(wǎng)絡(luò)相關(guān)的業(yè)務(wù)邏輯起惕,向創(chuàng)建一個實體的對象或者從網(wǎng)絡(luò)獲取對象涡贱。為了這個目的,你需要用到一些
Services
和Managers
惹想,這些不能算是VIPER的一部分问词,更確切的說只是些外部依賴。展示器(Presenter)——包含與
UI
相關(guān)(但是獨立于UIKit
)的業(yè)務(wù)邏輯嘀粱,調(diào)用交互器(Interactor
)中的方法激挪。實體(Entities)——簡單地數(shù)據(jù)對象辰狡,并不是數(shù)據(jù)訪問層,因為數(shù)據(jù)訪問是交互器(
Interactor
)的職責(zé)垄分。路由(Router)——用來連接
VIPER
中的模塊宛篇。
大致上說,VIPER
模塊可以是一個界面薄湿,也可以是整個應(yīng)用的用戶界面(user story)——想象一下驗證功能叫倍,它可以是一個界面也可以是幾個相關(guān)聯(lián)的界面〔蛄觯”樂高積木“塊應(yīng)該多大呢吆倦?——這取決你自己。
如果我們同MV(X)
這一類進行比較坐求,我們可以看到幾個不同的職責(zé)解耦之處:
模型(
Model
)(數(shù)據(jù)交互(data interaction))邏輯轉(zhuǎn)移到了交互器(Interactor
)中蚕泽,同時實體(Entities
)作為單一的數(shù)據(jù)結(jié)構(gòu)存在。只有控制器/展示器/視圖模型的UI展示責(zé)任轉(zhuǎn)移到了展示器(Presenter)桥嗤,而不是數(shù)據(jù)修改功能须妻。
VIPER
是第一個明確的負(fù)責(zé)導(dǎo)航功能的架構(gòu)模式,這點是通過路由(Router
)來解決的砸逊。
在iOS應(yīng)用中璧南,尋一個適當(dāng)?shù)姆绞竭M行頁面路由是一個具有挑戰(zhàn)性的工作,而MV(X)
這類模式只是簡單的避而不談师逸。
這個例子沒有涉及到路由和模塊間的交互司倚,因為,這些話題在MV(X)
這類模式中也沒有提及篓像。
import UIKIt
struct Person { // 實體(通常要比這個復(fù)雜动知,例如:NSManagedObject)
let firstName: String
let lastName: String
}
struct GreetingData { // 傳遞數(shù)據(jù)結(jié)構(gòu)(不是實體)
let greeting: String
let subject: String
}
protocol GreetingOutput: class {
func receiveGreetingData(greetingData: GreetingData)
}
class GreetingInteractor: GreetingProvider {
weak var output: GreetingOutput!
func provideGreetingData() {
let person = Person(firstName: "David", lastName: "Blaine")// 通常來自于數(shù)據(jù)訪問層
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
}
let view = GreetingViewController()
let presenter = GreetingPresenter()
let interactor = GreetingInteractor()
view.eventHandler = presenter
presenter.greetingProvider = interactor
interactor.output = presenter
再來看一下特點評估:
解耦(Distribution)——毋庸置疑,VIPER架構(gòu)在職責(zé)間解耦的表現(xiàn)最好员辩。
可測試性(Testability)——不足為奇盒粮,更好的解耦,更好的可測試性奠滑。
易用性(Easy of use)——最后丹皱,上述兩個的表現(xiàn)所花費的代價你已經(jīng)猜出來了。你不得不寫大量的沒有多少職責(zé)的接口(interface)類宋税。
樂高積木提現(xiàn)在哪里呢摊崭?
當(dāng)使用VIPER
時,感覺就像用樂高積木搭建一座帝國大廈一樣杰赛,這是一個有問題的信號呢簸。也許,對于你的應(yīng)用來說現(xiàn)在使用VIPER
架構(gòu)還為時過早,你可以考慮一個簡單的架構(gòu)根时。有些人則選擇忽略這個問題瘦赫,還繼續(xù)大炮打麻雀——大材小用。我猜測他們覺得未來他們的應(yīng)用會因此而受益蛤迎,盡管現(xiàn)在維護成本高的不合情理确虱。如果你也這樣想的話,我建議你試一下Generamba——一個可以生成VIPER
架構(gòu)的工具替裆。盡管如此蝉娜,對我個人來說,這樣就像在使用有自動瞄準(zhǔn)系統(tǒng)的大炮一樣而不是簡單地投石機扎唾。
結(jié)論
我們已經(jīng)看過了幾種架構(gòu)模式召川,我希望大家都能為各自的困惑找到答案,毫無疑問你會意識到這篇文章并沒有提供什么高招胸遇,所以荧呐,選擇架構(gòu)模式的關(guān)鍵是根據(jù)具體的情況進行權(quán)衡、取舍纸镊。
因此倍阐,在同一個應(yīng)用中出現(xiàn)架構(gòu)混合是很正常的一件事。例如:你一開始用的是MVC
架構(gòu)逗威,突然你意識到有一個特定的界面很難再用MVC
架構(gòu)進行有效的維護了峰搪,然后你就把它轉(zhuǎn)換成了MVVM
架構(gòu)而且僅僅只是對這一個界面進行了轉(zhuǎn)換。對于其他的界面如果MVC
架構(gòu)工作正常的話沒有必要進行重構(gòu)凯旭,因為這兩個架構(gòu)很容易兼容概耻。
Make everything as simple as possible, but not simpler——Albert Einstein
盡可能的簡化一切,但并不簡單——阿爾伯特·愛因斯坦