一、概述
-
在 iOS 開發中,MVC(Model View Controller)是構建iOS App的標準模式,是蘋果推薦的一個用來組織代碼的權威范式。Apple甚至是這么說的。在MVC下,所有的對象被歸類為一個Model,一個View,和一個Controller。Model持有數據,View顯示與用戶交互的界面,而ViewController調解Model和View之間的交互。現在,MVC 依然是目前主流客戶端編程框架,但同時它也被調侃成Massive View Controller(重量級視圖控制器),想必開發者在開發中無可避免被下面幾個問題所困擾:
- 厚重的ViewController
- 遺失的網絡邏輯(無立足之地)
- 較差的可測試性
為了避免和解決上述問題的產生,從MVC引申出來一種維護性較強、耦合性低的新的架構MVVM(Model View View-Mode),MVVM正式規范了視圖和控制器緊耦合的性質,并引入新的組件。MVVM主要目的是為了分離視圖(View)和模型(Model)。
本文只是分享一下筆者對MVC和MVVM的一些見解,在此拋磚引玉,希望能為存在對MVC和MVVM迷茫的廣大開發者提供一點思路,少走一些彎路,填補一些細坑。文章僅供大家參考,若有不妥之處,還望不吝賜教,歡迎批評指正。
二、MVC(Model View Controller)
- MVC之間的關系
任何一個正經開發過軟件的人都熟悉MVC
,它意思是Model View Controller, 是一個在復雜應用設計中組織代碼的公認模式,它們之間的結構關系如下:
MVC示意圖.png
我們看到的只是一個蘋果 **典型的MVC ** 設置。view
將用戶交互通知給controller
。view controller
通過更新model
來反應狀態的改變。model
(通常使用Key-Value-Observation
)通知controller
來更新他們負責的view
。大多數iOS應用程序的代碼使用這種方式來組織。然而,典型的MVC架構
不適用于當下的iOS開發。盡管從技術上看View
和 Controller
是相互獨立的,但事實上它們幾乎總是結對出現,一個 View
只能與一個 Controller
進行匹配,反之亦然。既然如此,那我們為何不正規化它們的連接:
因此,M-VC 可能是對 iOS 開發中的 MVC
模式更為準確的解讀,同時更也準確地描述了我們日常開發可能已經編寫的 MVC
代碼,但它并沒有做太多事情來解決 iOS 應用中日益增長的重量級視圖控制器的問題。(PS:躺槍了沒...)
舉例說明:
若假設筆者利用MVC
的設計模式來開發此界面,那想必是這樣的。
M:SUGoods(商品模型Model)
V:SUGoodsCell(展示商品數據的View,自定義的 UITableViewCell)
C:SUHomeViewController (首頁控制器Controller)
控制器(SUHomeViewController
)代碼實現
- (void) requestRemoteData
{
// 1.發起網絡請求,獲取到服務器的數據,并將其轉化成模型數據(`SUGoods`)
// 2.添加到數據源(`dataSource`)
// 3.最后刷新表格`[self.tableView reloadData]`,配置`SUGoodsCel`l即可
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
SUGoodsCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Goods"];
cell.goods = self.dataSource[indexPath.row];
return cell;
}
View(SUGoodsCell)
代碼實現
SUGoodsCell.h
@class SUGoods;
@interface SUGoodsCell : UITableViewCell
@property (nonatomic, strong) SUGoods *goods;
@end
SUGoodsCell.m
@implementation SUGoodsCell
- (void)setGoods:(SUGoods *)goods
{
_goods = goods
/// config data ....
}
@end
都寫到這份上了,大家用腳趾頭想想,這個SUGoodsCell
,正是由View
直接來調用Model
,所以事實上典型的MVC
的原則已經違背了,但是這種情況是一直發生的甚至于人們不覺得這里有哪些不對。如果嚴格遵守MVC
的話,你會把對cell
的設置放在Controller
中,不向View
傳遞一個Model
對象,這樣就會大大增加Controller的體積
。所以說,這哪里是典型的MVC
設計模式,這分明是M-VC
設計模式呀。簡而言之,在理想的世界里,MVC
也許工作的很好。然而,我們生活在真實的世界,謝謝(PS:讓夢想實現的最好的方式,就是醒來!!!)。
- MVC的弊端
MVC
的利弊大家想必是有目共睹的,Massive View Controller
的說法也并非空穴來風的。讓我們一起探討MVC
的弊端,剖析問題產生原因,打造一個輕量級的ViewController
,明確MVC
設計模式中各個角色的職責。
厚重的View Controller
M:模型model的對象通常非常的簡單。根據Apple的文檔,model
應包括數據
和操作數據的業務邏輯
。而在實踐中,model
層往往非常薄,不管怎樣,model
層的業務邏輯不應被拖入到controller
。
V:視圖view
通常是UIKit
控件(component
,這里根據習慣譯為控件)或者編碼定義的UIKit
控件的集合。View
的如何構建(PS:IB或者手寫界面
)何必讓Controller
知曉,同時View
不應該直接引用model
(PS:現實中,你懂的!),并且僅僅通過IBAction事件
引用controller
。業務邏輯很明顯不歸入view
,視圖本身沒有任何業務。
C:控制器controller
。Controller
是app的“膠水代碼”:協調模型和視圖之間的所有交互。控制器負責管理他們所擁有的視圖的視圖層次結構,還要響應視圖的loading
、appearing
、disappearing
等等,同時往往也會充滿我們不愿暴露的model的模型邏輯
以及不愿暴露給視圖的業務邏輯
。
網絡數據的請求及后續處理,本地數據庫操作,以及一些帶有工具性質輔助方法都加大了Massive View Controller
的產生。遺失(無處安放)的網絡邏輯
蘋果使用的MVC
的定義是這么說的:所有的對象都可以被歸類為一個model
,一個view
,或是一個controller
。
你可能試著把它放在Model
對象里,但是也會很棘手,因為網絡調用應該使用異步,這樣如果一個網絡請求比持有它的model
生命周期更長,事情將變的復雜。顯然View
里面做網絡請求那就更格格不入了,因此只剩下Controller
了。若這樣,這又加劇了Massive View Controller
的問題。若不這樣,何處才是網絡邏輯
的家呢?較差的可測試性
由于View Controller
混合了視圖處理邏輯和業務邏輯,分離這些成分的單元測試成了一個艱巨的任務。若一個Massive View Controller
有上萬行代碼,要你編寫個單元測試,我敢保證,你不是想寫,你是想死,分分鐘填表走人。
三、MVVM(Model View View-Mode)
一種可以很好地解決Massive View Controller
問題的辦法就是將 Controller
中的展示邏輯抽取出來,放置到一個專門的地方,而這個地方就是 viewModel
。MVVM
衍生于MVC
,是對 MVC
的一種演進,它促進了 UI 代碼與業務邏輯的分離。它正式規范了視圖和控制器緊耦合的性質,并引入新的組件。他們之間的結構關系如下:
-
MVVM 的基本概念
- 在
MVVM
中,view
和view controller
正式聯系在一起,我們把它們視為一個組件 -
view
和view controller
都不能直接引用model
,而是引用視圖模型(viewModel
) -
viewModel
是一個放置用戶輸入驗證邏輯,視圖顯示邏輯,發起網絡請求和其他代碼的地方 - 使用
MVVM
會輕微的增加代碼量,但總體上減少了代碼的復雜性
- 在
-
MVVM 的注意事項
-
view
引用viewModel
,但反過來不行(即不要在viewModel
中引入#import UIKit.h
,任何視圖本身的引用都不應該放在viewModel
中)(PS:基本要求,必須滿足) -
viewModel
引用model
,但反過來不行
-
-
MVVM 的使用建議
-
MVVM
可以兼容你當下使用的MVC
架構。 -
MVVM
增加你的應用的可測試性。 -
MVVM
配合一個綁定機制效果最好(PS:ReactiveCocoa你值得擁有)。 -
viewController
盡量不涉及業務邏輯,讓viewModel
去做這些事情。 -
viewController
只是一個中間人,接收view
的事件、調用viewModel
的方法、響應viewModel
的變化。 -
viewModel
絕對不能包含視圖view(UIKit.h)
,不然就跟view
產生了耦合,不方便復用和測試。 -
viewModel
之間可以有依賴。 -
viewModel
避免過于臃腫,否則重蹈Controller
的覆轍,變得難以維護。
-
-
MVVM 的優勢
- 低耦合:
View
可以獨立于Model
變化和修改,一個viewModel
可以綁定到不同的View
上 - 可重用性:可以把一些視圖邏輯放在一個
viewModel
里面,讓很多view
重用這段視圖邏輯 - 獨立開發:開發人員可以專注于業務邏輯和數據的開發
viewModel
,設計人員可以專注于頁面設計 - 可測試:通常界面是比較難于測試的,而
MVVM
模式可以針對viewModel
來進行測試
- 低耦合:
-
MVVM 的弊端
- 數據綁定使得
Bug
很難被調試。你看到界面異常了,有可能是你View
的代碼有Bug
,也可能是Model
的代碼有問題。數據綁定使得一個位置的Bug
被快速傳遞到別的位置,要定位原始出問題的地方就變得不那么容易了。 - 對于過大的項目,數據綁定和數據轉化需要花費更多的內存(成本)。主要成本在于:
- 數組內容的轉化成本較高:數組里面每項都要轉化成
Item
對象,如果Item對象中還有類似數組,就很頭疼。 - 轉化之后的數據在大部分情況是不能直接被展示的,為了能夠被展示,還需要第二次轉化。
- 只有在API返回的數據高度標準化時,這些對象原型(
Item
)的可復用程度才高,否則容易出現類型爆炸,提高維護成本。 - 調試時通過對象原型查看數據內容不如直接通過
NSDictionary/NSArray
直觀。 - 同一API的數據被不同View展示時,難以控制數據轉化的代碼,它們有可能會散落在任何需要的地方。
- 數組內容的轉化成本較高:數組里面每項都要轉化成
- 數據綁定使得
四、總結
-
MVC
的設計模式也并非是病入膏肓,無藥可救的架構,最起碼目前MVC
設計模式仍舊是iOS開發的主流框架,存在即合理。針對文章所述的弊端,我們依舊有許多可行的方法去避免和解決,從而打造一個輕量級的ViewController
。 -
MVVM
是MVC
的升級版,完全兼容當前的MVC
架構,MVVM
雖然促進了UI 代碼與業務邏輯的分離,一定程度上減輕了ViewController
的臃腫度,但是View
和ViewModel
之間的數據綁定使得MVVM
變得復雜和難用了,如果我們不能更好的駕馭兩者之間的數據綁定,同樣會造成Controller
代碼過于復雜,代碼邏輯不易維護的問題。 - 一個輕量級的
ViewController
是基于MVC
和MVVM
模式進行代碼職責的分離而打造的。MVC
和MVVM
有優點也有缺點,但缺點在他們所帶來的好處面前時不值一提的。他們的低耦合性,封裝性,可測試性,可維護性和多人協作便利大大提高了開法效率。 - 同時,我們需要保持的是一個擁抱變化的心,以及理性分析的態度。在新技術的面前,不盲從,也不守舊,一切的決策都應該建立在認真分析的基礎上,這樣才能應對技術的變化。
五、期待
- 文章若對您有點幫助,請給個喜歡??,畢竟碼字不易;若對您沒啥幫助,請給點建議??,切記學無止境。
- 針對文章所述內容,閱讀期間任何疑問;請在文章底部批評指正,我會火速解決和修正問題。
- GitHub地址:https://github.com/CoderMikeHe
六、實戰篇
七、參考鏈接
- http://www.cnblogs.com/brycezhang/p/3840567.html
- http://www.mamicode.com/info-detail-989164.html
- http://blog.devtang.com/2015/11/02/mvc-and-mvvm/
- https://objccn.io/issue-13-1/
- http://www.cocoachina.com/ios/20160108/14916.html
- https://casatwy.com/iosying-yong-jia-gou-tan-wang-luo-ceng-she-ji-fang-an.html
- http://www.cocoachina.com/ios/20151020/13795.html