作為一個iOS程序員,MVC一定是我們耳熟能詳?shù)囊环N架構(gòu)模式,,而且當你的項目規(guī)模不大的時候,,MVC也確實有它的優(yōu)勢,它的開發(fā)效率確實是足夠高,。但當你的項目發(fā)展的一定的規(guī)模,,你會發(fā)現(xiàn)傳統(tǒng)的MVC模式會導致C層代碼量劇增,維護困難等一系列問題,,這個時候我們就需要考慮一些其它模式了,。
MV(X)的基本要素
常用的架構(gòu)模式
前面三種模式都由三個模塊組成:
Models —— 數(shù)據(jù)層,負責數(shù)據(jù)的處理,。 Views —— 展示層,,即所有的UI Controller/Presenter/ViewModele(控制器/展示器/視圖模型)——它們負責View與Mode之間的調(diào)配
MVC傳統(tǒng)的MVC
我們所熟知的MVC其實Apple給我們提供的Cocoa MVC,但其實MVC最先產(chǎn)生于Web,,它原來的樣子應該是這樣的
傳統(tǒng)MVC
在這種架構(gòu)下,,View是無狀態(tài)的,在Model變化的時候它只是簡單的被Controller重繪,,比如網(wǎng)頁中你點擊了一個新的鏈接,,整個頁面就重新加載。盡管這種MVC在iOS應該里面可以實現(xiàn),,但是由于MVC的三個模塊都緊密耦合了,,每一個模塊都和其它兩種模塊有聯(lián)系,所以即便是實現(xiàn)了也沒有什么意義,。這種耦合還降低了它們的可重用性,,所以,傳統(tǒng)的MVC在iOS中可以舍棄了,。 Apple的MVC
Cocoa MVC
Apple提供的MVC中,,View和Model之間是相互獨立的,它們只通過Controller來相互聯(lián)系,??上У氖荂ontroller得重用性太差,因為我們一般都把冗雜的業(yè)務邏輯放在了Controller中,。 現(xiàn)實中,,我們的MVC一般是這樣的
現(xiàn)實MVC
為什么會這樣呢?主要還是因為我們的UIViewController它本身就擁有一個VIew,這個View是所有視圖的根視圖,,而且View的生命周期也都由Controoler負責管理,,所以View和Controller是很難做到相互獨立的。雖然你可以把控制器里的一些業(yè)務邏輯和數(shù)據(jù)轉(zhuǎn)換工作交給Model,,但是你卻沒有辦法將一些工作讓View來分攤,,因為View的主要職責只是將用戶的操作行為交給Controller去處理而已。于是Controller最終就變成了所有東西的代理和數(shù)據(jù)源,,甚至還有網(wǎng)絡請求.....還有......所以我們寫的Controller代碼量一般都是非常大的,,隨著當業(yè)務需求的增加,Controller的代碼量會一直增長,,而相對來說View和Model的代碼量就比較穩(wěn)定,,所以也有人把MVC叫做Massive View Controller,因為Controller確實顯得有些臃腫,。 在這里關(guān)于Model的劃分,,其實有一個胖Model和瘦Model之分,它們的差別主要就是把Controller的部分數(shù)據(jù)處理職責交給了胖Model,。 胖Model(Fat Model): 胖Model包含了部分弱業(yè)務邏輯。胖Model要達到的目的是,,Controller從胖Model這里拿到數(shù)據(jù)之后,,不用做額外的操作或者只做非常少的操作就能將數(shù)據(jù)應用在View上。 FatModel做了這些弱業(yè)務之后,,Controller可以變得相對skinny一點,,它只需要關(guān)注強業(yè)務代碼。而強業(yè)務變動的可能性要比弱業(yè)務大得多,,弱業(yè)務相對穩(wěn)定,,所以弱業(yè)務塞給Model不會有太大問題。另一方面,,弱業(yè)務重復出現(xiàn)的頻率要大于強業(yè)務,,對復用性要求更高,如果這部分業(yè)務寫在Controller,,會造成代碼冗余,,類似的代碼會灑得到處都是,而且一旦弱業(yè)務有修改,,你就會需要修改所有地方,。如果塞到了Model中,就只需要改Model就夠了,。 但是胖Mpdel也不是就是沒有缺點的,,它的缺點就在于胖Model相對比較難移植,雖然只是包含弱業(yè)務,,但是它畢竟也是業(yè)務,,遷移的時候很容易拔出羅布帶出泥,,也就是說它耦合了它的業(yè)務。而且軟件是會成長的,,F(xiàn)atModel也很有可能隨著軟件的成長越來越Fat,,最后難以維護。 瘦Model(Slim Model): 瘦Model只負責業(yè)務數(shù)據(jù)的表達,,所有業(yè)務無論強弱一律人給Controller,。瘦Model要達到的目的是,盡一切可能去編寫細粒度Model,,然后配套各種helper類或者方法來對弱業(yè)務做抽象,,強業(yè)務依舊交給Controller。 由于Slim Model跟業(yè)務完全無關(guān),,它的數(shù)據(jù)可以交給任何一個能處理它數(shù)據(jù)的Helper或其他的對象,,來完成業(yè)務。在代碼遷移的時候獨立性很強,,很少會出現(xiàn)拔出蘿卜帶出泥的情況,。另外,由于SlimModel只是數(shù)據(jù)表達,,對它進行維護基本上是0成本,,軟件膨脹得再厲害,SlimModel也不會大到哪兒去,。缺點就在于,,Helper這種做法也不見得很好,由于Model的操作會出現(xiàn)在各種地方,,SlimModel很容易出現(xiàn)代碼重復,,在一定程度上違背了DRY(Don’t Repeat Yourself)的思路,Controller仍然不可避免在一定程度上出現(xiàn)代碼膨脹,。 綜上所述,,Cocoa MVC在各方面的表現(xiàn)如下: 劃分 - View 和 Model 確實是實現(xiàn)了分離,但是 View 和 Controller 耦合的太 厲害 可測性 - 因為劃分的不夠清楚,,所以能測的基本就只有 Model 而已 易用 - 相較于其他模式,,它的代碼量最少。而且基本上每個人都很熟悉它,,即便是沒太多經(jīng)驗的開發(fā)者也能維護,。
MVPMVP
看起來和Cocoa MVC很像,也確實很像,。但是,,在MVC中View和COntroller是緊密耦合的,而在MVP中,Presenter完全不關(guān)注ViewController的生命周期,,而且View也能被簡單mock出來,,所以在Presenter里面基本沒有什么布局相關(guān)的代碼,它的職責只是通過數(shù)據(jù)和狀態(tài)更新View,。 而且在MVP中,,UIVIewController的那些子類其實是屬于View的。這樣就提供了更好的可測性,,只是開發(fā)速度會更高,,因為你必須手動去創(chuàng)建數(shù)據(jù)和綁定事件。 下面我寫了個簡單的Demo MVPDemo
由于這里主要是學習架構(gòu)模式思想,,所以我的命名簡單粗暴,,希望大家理解。
界面1
界面也很簡單,,就是通過點擊按鈕修改兩個label顯示的內(nèi)容 Model很簡單,,就是一個數(shù)據(jù)結(jié)構(gòu),但在實際應用中,,你可以將網(wǎng)絡請求等一些數(shù)據(jù)處理放在這里
@interface Model : NSObject @property (nonatomic, strong) NSString *first; @property (nonatomic, strong) NSString *second; @end
要讓Presenter和View通信,,所以我們定義一個協(xié)議,以實現(xiàn)Presenter向View發(fā)送命令
@protocol MyProtocol <NSObject> - (void)setFirst:(NSString *)first; - (void)setSecond:(NSString *)second; @end
view/VIewController,,實現(xiàn)該協(xié)議
.h @interface ViewController : UIViewController @property (nonatomic, strong) UILabel *firstLabel; @property (nonatomic, strong) UILabel *secondLabel; @property (nonatomic, strong) UIButton *tapButton; @end .m主要代碼 - (void)viewDidLoad { [super viewDidLoad]; [self.view addSubview:self.firstLabel]; [self.view addSubview:self.secondLabel]; [self.view addSubview:self.tapButton]; self.presenter = [Presenter new]; [self.presenter attachView:self]; } - (void)buttonClicked{ [self.presenter reloadView]; } - (void)setFirst:(NSString *)first{ self.firstLabel.text = first; } - (void)setSecond:(NSString *)second{ self.secondLabel.text = second; }
Presenter
.h @interface Presenter : NSObject - (void)attachView:(id <MyProtocol>)attachView; - (void)reloadView; @end .m @interface Presenter() @property (nonatomic, weak) id <MyProtocol> view; @property (nonatomic, strong) Model *model; @end @implementation Presenter - (instancetype)init { self = [super init]; if (self) { self.model = [Model new]; self.model.first = @'first'; self.model.second = @'second'; } return self; } - (void)attachView:(id<MyProtocol>)attachView{ self.view = attachView; } - (void)reloadView{ //可以在這里做一些數(shù)據(jù)處理 [self.view setFirst:self.model.first]; [self.view setSecond:self.model.second]; } @end
這里只是一個簡單的Demo,,其實思想很簡單,就是講業(yè)務邏輯交給Presenter,,而Presenter以命令的形式來控制View。
一些說明:
MVP架構(gòu)擁有三個真正獨立的分層,,所以在組裝的時候會有一些問題,,而MVP也成了第一個披露這種問題的架構(gòu),因為我們不想讓View知道Model的信息,,所以在當前的Controller去組裝是不正確的,,我們應該在另外的地方完成組裝。比如我們可以創(chuàng)建一個應用層的Router服務,,讓它來負責組裝和View-to-View的轉(zhuǎn)場,。這個問題下很多模式中都存在。 下面總結(jié)一下MVP的各方面表現(xiàn): 劃分——我們把大部分職責都分配到了Presenter和Model里面,,而View基本不需要做什么 可測性——我們可以通過View來測試大部分業(yè)務邏輯 易用——代碼量差不多是MVC架構(gòu)的兩倍,,但是MVP的思路還是蠻清晰的
另外,MVP還有一個變體,,它的不同主要就是添加了數(shù)據(jù)綁定,。這個版本的MVP的View和Model直接綁定,而Presenter仍然繼續(xù)處理View上的用戶操作,控制View的顯示變化,。這種架構(gòu)和傳統(tǒng)的MVC類似,,所以我們基本可以舍棄。 MVVM
MVVM可以說是MV(X)系列中最新興起的也是最出色的一種架構(gòu),,而它也廣受我們iOS程序員喜愛,。 MVVM
MVVM和MVP很像: 把ViewController看成View View和Model之間沒有緊耦合
另外它還讓VIew和ViewModel做了數(shù)據(jù)綁定。ViewModel可以調(diào)用對Model做更改,,也可以再Model更新的時候?qū)ψ陨磉M行調(diào)整,,然后通過View和ViewModel之間的綁定,對View進行相應的更新,。 關(guān)于綁定
在iOS平臺上面有KVO和通知,,但是用起來總是覺得不太方便,所以有一些三方庫供我們選擇: 基于KVO的綁定庫,,如 RZDataBinding 或者 SwiftBond 使用全量級的 函數(shù)式響應編程 框架,比如ReactiveCocoaRxSwift 或者PromiseKit
實際上,,我們在提到MVVM的時候就很容易想到ReactiveCocoa,它也是我們在iOS中使用MVVM的最好工具,。但是相對來說它的學習成本和維護成本 也是比較高的,,而且一旦你應用不當,很可能造成災難性的問題,。 下面我暫時不用RAC來簡單展示一下MVVM: MVVM
界面很簡單,,就是點擊一個button修改label里面的數(shù)據(jù) 界面
Model
@interface MVVMModel : NSObject @property (nonatomic, copy) NSString *text; @end @implementation MVVMModel - (NSString *)text{ _text = [NSString stringWithFormat:@'newText%d',rand()]; return _text; }
ViewModel
@interface MVVMViewModel : NSObject - (void)changeText; @end @interface MVVMViewModel() @property (nonatomic, strong) NSString *text; @property (nonatomic, strong) MVVMModel *model; @end @implementation MVVMViewModel - (instancetype)init { self = [super init]; if (self) { self.model = [MVVMModel new]; } return self; } - (void)changeText{ self.text = self.model.text;; }
Controller
@interface MVVMViewController () @property (weak, nonatomic) IBOutlet UILabel *textLabel; @property (nonatomic, strong) MVVMViewModel *viewModel; @end @implementation MVVMViewController - (void)viewDidLoad { [super viewDidLoad]; self.viewModel = [[MVVMViewModel alloc]init]; [self.viewModel addObserver:self forKeyPath:@'text' options:NSKeyValueObservingOptionNew context:nil]; } - (IBAction)buttonClicked:(UIButton *)sender { [self.viewModel changeText]; } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{ self.textLabel.text = change[@'new']; }
MVVM的核心就是View和ViewModel的一個綁定,這里我只是簡單的通過KVO實現(xiàn),,看起來并不是那么優(yōu)雅,,想要深度使用的話我覺得還是有必要學習一下RAC的,完整的Demo(https://github.com/RhettTamp/MVXDemo)
下面我們再來對MVVM的各方面表現(xiàn)做一個評價: 劃分——MVVM 框架里面的 View 比 MVP 里面負責的事情要更多一些,。因為前者是通過 ViewModel 的數(shù)據(jù)綁定來更新自身狀態(tài)的,,而后者只是把所有的事件統(tǒng)統(tǒng)交給 Presenter 去處理就完了,自己本身并不負責更新,。 可測性—— 因為 ViewModel 對 View 是一無所知的,,這樣我們對它的測試就變得很簡單。View 應該也是能夠被測試的,,但是可能因為它對 UIKit 的依賴,,你會直接略過它。 易用——它比MVP會更加簡潔,,因為在 MVP 下你必須要把 View 的所有事件都交給 Presenter 去處理,,而且需要手動的去更新 View 的狀態(tài);而在 MVVM 下,,你只需要用綁定就可以解決,。
綜上:MVVM 真的很有魅力,,因為它不僅結(jié)合了上述幾種框架的優(yōu)點,還不需要你為視圖的更新去寫額外的代碼(因為在 View 上已經(jīng)做了數(shù)據(jù)綁定),,另外它在可測性上的表現(xiàn)也依然很棒,。 為了簡單易懂,以上的Demo都非常簡潔,,不知道看了這篇博客能否加深你對MV(X)的一些理解,,這些理解也僅作為我個人的一些參考,有什么不對的地方希望大家指出,。
|