iOS架構淺談從 MVC、MVP 到 MVVM
概述
做了這麼多年的客戶端研發一直在使用蘋果爸爸推薦的MVC架構模式。MVC從應用層面進行分層開發,極大最佳化了我們的程式碼結構,簡單易上手,很容易被程式設計師所接受。程式設計師剛接手一個新專案,如果是MVC的架構模式,會減少程式碼熟悉時間,快速的進行開發和維護工作,實際上對於多人開發維護的專案,MVC仍然是非常好的架構模式,這也是這種架構模式經久不衰的原因。
但是任何事物都有兩面性,隨著專案需求的增加,業務邏輯、網路請求、代理方法等都往Controller層加塞,導致Controller層變得越來越臃腫,動輒上千行的程式碼量絕對是維護人員的噩夢,因此在MVC基礎上逐漸衍生出來了MVP、MVVM等架構模式。
本文是基於OC
程式碼進行闡述的,使用iOS開發經典的 TableView
列表來分析每個架構模式。相信看了這篇文章你會有所領悟。當然一千個人眼中有一千種哈姆雷特,具體在業務開發中使用哪種模式需要你自己去衡量。
1.傳統的MVC
設計模式
M
: Model 資料層,負責網路資料的處理,資料持久化儲存和讀取等工作V
: View 檢視層,負責呈現從資料層傳遞的資料渲染工作,以及與使用者的互動工作C
: Controller控制器,負責連線Model層跟View層,響應View的事件和作為View的代理,以及介面跳轉和生命週期的處理等任務
使用者的互動邏輯
使用者點選 View(檢視) --> 檢視響應事件 -->透過代理傳遞事件到Controller–>發起網路請求更新Model—>Model處理完資料–>代理或通知給Controller–>改變檢視樣式–>完成
可以看到Controller強引用View與Model,而View與Model是分離的,所以就可以保證Model和View的可測試性和複用性,但是Controller不行,因為Controller是Model和View的中介,所以不能複用,或者說很難複用。
iOS開發實際使用的MVC架構
在我們實際開發中使用的MVC模式可以看到,View與Controller耦合在一起了。這是由於每一個介面的建立都需要一個Controller,而每一個Controller裡面必然會帶一個View,這就導致了C和V的耦合。這種結構確實可以提高開發效率,但是一旦介面複雜就會造成Controller變得非常臃腫和難以維護。
MVC程式碼示例
我們要實現一個簡單的列表頁面,每行cell都一個按鈕,點選按鈕前面數字➕1操作。
核心程式碼:
// Controller
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
__weak typeof(self) wealSelf = self;
MVCTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell_identifer"];
if(cell == nil){
cell = [[MVCTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell_identifer"];
}
DemoModel *model = self.dataArray[indexPath.row];
[cell loadDataWithModel:model];
cell.clickBtn = ^{
NSLog(@"id===%ld",model.num);
[wealSelf changeNumWithModel:model];
};
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
/*
* 使用者點選事件透過Block傳遞過來後,在Controller層處理更新Mdoel以及更新檢視的邏輯
*/
- (void)changeNumWithModel:(DemoModel*)model{
model.num++;
NSIndexPath *path = [NSIndexPath indexPathForRow:model.Id inSection:0];
[self.mainTabelView reloadRowsAtIndexPaths:@[path] withRowAnimation:UITableViewRowAnimationLeft];
}
複製程式碼
- 可以看到使用者點選事件透過Block傳遞過來後,在Controller層處理更新Mdoel以及更新檢視的邏輯
2.MVP
設計模式
M
: Model 資料層,負責網路資料的處理,資料持久化儲存和讀取等工作V
: View 檢視層,負責呈現從資料層傳遞的資料渲染工作,以及與使用者的互動,這裡把Controller層也合併到檢視層P
: Presenter層,負責檢視需要資料的獲取,獲取到資料後重新整理檢視。響應View的事件和作為View的代理。
可以看到 MVP模式跟原始的MVC模式非常相似,完全實現了View與Model層的分離,而且把業務邏輯放在了Presenter層中,檢視需要的所有資料都從Presenter獲取,而View與 Presenter透過協議進行事件的傳遞。
使用者的互動邏輯
使用者點選 View(檢視) --> 檢視響應事件 -->透過代理傳遞事件到Presenter–>發起網路請求更新Model–>Model處理完資料–>代理或通知給檢視(View或是Controller)–>改變檢視樣式–>完成
MVP程式碼示例
//DemoProtocal
import
@protocol DemoProtocal
@optional
//使用者點選按鈕 觸發事件: UI改變傳值到model資料改變 UI --- > Model 點選cell 按鈕
-(void)didClickCellAddBtnWithIndexPathRow:(NSInteger)index;
//model資料改變傳值到UI介面重新整理 Model --- > UI
-(void)reloadUI;
@end
複製程式碼
- 我們把所有的代理抽象出來,成為一個Protocal檔案。這兩個方法的作用:
-
-(void)didClickCellAddBtnWithIndexPathRow:(NSInteger)index;
:Cell檢視呼叫它去Presenter層實現點選邏輯的處理 -
-(void)reloadUI;
: Presenter呼叫它去更新主檢視View或者Controller
//Presenter.h
#import
#import
#import "DemoProtocal.h"
NS_ASSUME_NONNULL_BEGIN
@interface Presenter : NSObject
@property (nonatomic, strong,readonly) NSMutableArray *dataArray;
@property (nonatomic, weak) iddelegate;//協議,去更新主檢視UI
// 更新 TableView UI 根據需求
- (void)requestDataAndUpdateUI;
//更新 cell UI
- (void)updateCell:(UITableViewCell*)cell withIndex:(NSInteger)index;
@end
複製程式碼
-
dataArray
: 檢視需要的資料來源 -
- (void)requestDataAndUpdateUI;
:主檢視Controller呼叫,去更新自己的UI -
- (void)updateCell:(UITableViewCell*)cell withIndex:(NSInteger)index;
:更新 Cell的UI
//Controller 層
- (void)iniData{
self.presenter = [[Presenter alloc] init];
self.presenter.delegate = self;
[self.presenter requestDataAndUpdateUI];
}
...
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
return self.presenter.dataArray.count;
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
MVPTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell_identifer"];
if(cell == nil){
cell = [[MVPTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell_identifer"];
}
//更新cell UI 資料
[self.presenter updateCell:cell withIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
#pragma mark - DemoProtocal
//Presenter 的代理回撥 資料更新了通知View去更新檢視
- (void)reloadUI{
[self.mainTabelView reloadData];
}
複製程式碼
- Controller層初始化Presenter,呼叫其方法更新自己的UI,可以看到網路資料的獲取,處理都在Presenter中,處理完成後透過協議回撥給Controller去reload資料
//Cell
- (void)addBtnDown:(UIButton*)btn{
NSLog(@"%s",__func__);
if([self.delegate respondsToSelector:@selector(didClickCellAddBtnWithIndexPathRow:)]){
[self.delegate didClickCellAddBtnWithIndexPathRow:self.index];
}
}
複製程式碼
- Cell層點選事件透過協議呼叫,而這個協議方法的實現是在Presenter中實現的。
MVP模式也有自身的缺點,所有的使用者操作和更新UI的回撥需要定義,隨著互動越來越複雜,這些定義都要有很大一坨程式碼。邏輯過於複雜的情況下,Present本身也會變得臃腫。所以衍生出了MVVM模式。
3.MVVM+RAC
設計模式
M
: Model 資料層,負責網路資料的處理,資料持久化儲存和讀取等工作V
: View 檢視層,此時的檢視層包括Controller,負責呈現從資料層傳遞的資料渲染工作,以及與使用者的互動VM
:ViewModel層,負責檢視需要資料的獲取,獲取到資料後重新整理檢視。響應View的事件和作為View的代理等工作。
透過架構圖可以看到,MVVM模式跟MVP模式基本類似。主要區別是在MVP基礎上加入了雙向繫結機制。當被繫結物件某個值的變化時,繫結物件會自動感知,無需被繫結物件主動通知繫結物件。可以使用KVO和RAC實現。我們這裡採用了RAC的實現方式。關於RAC如果不熟悉的小夥伴可以點,我們這篇文章不在涉及。
MVVM程式碼示例
我們這裡包括兩層檢視:主檢視Controller以及Cell,分別對應兩層ViewModel:ViewModel和CellViewModel
//ViewModel.h
@interface ViewModel : NSObject
//傳送資料請求的Rac,可以去訂閱獲取 請求結果
@property (nonatomic,strong,readonly) RACCommand *requestCommand;
@property (nonatomic,strong) NSArray *dataArr;//返回子級物件的ViewModel
- (CellViewModel *)itemViewModelForIndex:(NSInteger)index;
@end
複製程式碼
-
RACCommand *requestCommand
:提供供主檢視呼叫的命令,呼叫它去獲取網路資料 -
NSArray *dataArr
: 提供供主檢視使用的資料來源,注意這裡不能用NSMutableArray,因為NSMutableArray不支援KVO,不能被RACObserve。 -
- (CellViewModel *)itemViewModelForIndex:(NSInteger)index;
根據Cell的index返回它需要的的ViewModel
@interface CellViewModel : NSObject
@property (nonatomic,copy,readonly) NSString *titleStr;
@property (nonatomic,copy,readonly) NSString *numStr;
@property (nonatomic,copy,readonly) RACCommand *addCommand;
- (instancetype)initWithModel:(DemoModel *)model;
@end
複製程式碼
-
CellViewModel
: 暴露出Cell渲染需要的所有資料 -
RACCommand *addCommand;
: 按鈕點選事件的指令,觸發後需要在CellViewModel裡面做處理。
//controller
- (void)iniData{
self.viewModel = [[ViewModel alloc] init];
// 傳送請求
RACSignal *signal = [self.viewModel.requestCommand execute:@{@"page":@"1"}];
[signal subscribeNext:^(id x) {
NSLog(@"x=======%@",x);
if([x boolValue] == 1){//請求成功
[self.mainTabelView reloadData];
}
}];
}
- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
MVVMTableVIewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell_identifer"];
if(cell == nil){
cell = [[MVVMTableVIewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell_identifer"];
}
//更新cell UI 資料
cell.cellViewModel = [self.viewModel itemViewModelForIndex:indexPath.row];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
複製程式碼
-
iniData
:初始化ViewModel,併傳送請求命令。這裡可以監聽這個完成訊號,進行重新整理檢視操作 -
cell.cellViewModel = [self.viewModel itemViewModelForIndex:indexPath.row];
根據主檢視的ViewModel去獲取Cell的ViewModel,實現cell的資料繫結。
//TableViewCell
RAC(self.titleLabel,text) = RACObserve(self, cellViewModel.titleStr);
RAC(self.numLabel,text) = RACObserve(self, cellViewModel.numStr);
[[self.addBtn rac_signalForControlEvents:UIControlEventTouchUpInside] subscribeNext:^(id x) {
NSLog(@">>>>>");
[self.cellViewModel.addCommand execute:nil];
}];
複製程式碼
- 在Cell裡面進行與ViewModel的資料繫結,這邊有個注意Racobserve左邊只有self右邊才有viewModel.titleStr這樣就避Cell重用的問題。
-
[self.cellViewModel.addCommand execute:nil];
:按鈕的點選方法觸發,事件的處理在CellViewModel中。
總結
- 經過幾十年的發展和演變MVC模式出現了各種各樣的變種,並在不同的平臺上有著自己的實現。在實際專案開發中,根據具體的業務需求找到適合的架構才是最好的,架構本身並沒有好壞之分。
- 最後對文中的MVC、MVP、MVVM架構的描述也摻雜了作者的主觀意見,如果對文中的內容有疑問,歡迎提出不同的意見進行討論。
作者:QiShare
連結:
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1868/viewspace-2797149/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- iOS MVC、MVVM、MVP架構模式淺淺析iOSMVCMVVMMVP架構模式
- 淺談MVC/MVP/MVVM模式(概述)MVCMVPMVVM模式
- iOS 架構模式–解密 MVC,MVP,MVVM以及VIPER架構iOS架構模式解密MVCMVPMVVM
- 淺談框架模式 MVC、MVP 和 MVVM框架模式MVCMVPMVVM
- [譯]iOS架構模式——解密MVC、MVP、MVVM和VIPERiOS架構模式解密MVCMVPMVVM
- iOS VIPER架構實踐(一):從MVC到MVVM到VIPERiOS架構MVCMVVM
- 淺談Android中的mvc,mvp,mvvmAndroidMVCMVPMVVM
- iOS架構設計:揭祕MVC, MVP, MVVM以及VIPERiOS架構MVCMVPMVVM
- 雜談: MVC/MVP/MVVMMVCMVPMVVM
- iOS底層原理 MVC、MVP、MVVM、分層設計淺談 — (13)iOSMVCMVPMVVM
- MVC、MVP、MVVM,談談我對Android應用架構的理解MVCMVPMVVMAndroid應用架構
- 架構設計的歷史·MVC·MVP·MVVM架構MVCMVPMVVM
- 談談對MVC、MVP和MVVM的理解?MVCMVPMVVM
- MVC,MVP,MVVMMVCMVPMVVM
- MVC——MVP——MVVMMVCMVPMVVM
- MVC、MVP、MVVMMVCMVPMVVM
- 探索從 MVC 到 MVVM + Flux 架構模式的轉變MVCMVVMUX架構模式
- Android 架構設計:MVC、MVP、MVVM和元件化Android架構MVCMVPMVVM元件化
- MVC和MVVM,MVPMVCMVVMMVP
- iOS架構由淺入深 | MVVMiOS架構MVVM
- 轉享:表現層架構模式比較:MVP(SC),MVP(PV),PM,MVVM 和 MVC架構模式MVPMVVMMVC
- 淺析前端開發中的 MVC/MVP/MVVM 模式前端MVCMVPMVVM模式
- MVC、MVCS、MVVM、MVP、VIPER等這麼多架構模式哪一個好呢?MVCMVVMMVP架構模式
- MVC、MVP和MVVM的區別MVCMVPMVVM
- 深入分析MVC、MVP、MVVM、VIPERMVCMVPMVVM
- MVC,MVP 和 MVVM 的圖示MVCMVPMVVM
- MVC MVP 和 MVVM 的圖示MVCMVPMVVM
- 教你認清MVC,MVP和MVVMMVCMVPMVVM
- MVC vs. MVP vs. MVVMMVCMVPMVVM
- 理解並運用MVC,MVP,MVVMMVCMVPMVVM
- 前端面試查漏補缺--(十一) 前端軟體架構模式MVC/MVP/MVVM前端面試架構模式MVCMVPMVVM
- 從MVC到DDD的架構演進MVC架構
- 不要再問我MVC、MVP、MVVM了MVCMVPMVVM
- MVC,MVP 和 MVVM 模式如何選擇?MVCMVPMVVM模式
- MVC、MVP和MVVM以及MVA比較MVCMVPMVVM
- 淺談MVPMVP
- MVP那些事兒 (2) 初探MVC架構MVPMVC架構
- iOS開發-MVP架構模式iOSMVP架構模式