iOS架構(gòu)從?MVC、MVP?到?MVVM
概述
做了這么多年的客戶端研發(fā)一直在使用蘋果爸爸推薦的MVC架構(gòu)模式。MVC從應用層面進行分層開發(fā),極大優(yōu)化了我們的代碼結(jié)構(gòu),簡單易上手,很容易被程序員所接受。程序員剛接手一個新項目,如果是MVC的架構(gòu)模式,會減少代碼熟悉時間,快速的進行開發(fā)和維護工作,實際上對于多人開發(fā)維護的項目,MVC仍然是非常好的架構(gòu)模式,這也是這種架構(gòu)模式經(jīng)久不衰的原因。
但是任何事物都有兩面性,隨著項目需求的增加,業(yè)務邏輯、網(wǎng)絡請求、代理方法等都往Controller層加塞,導致Controller層變得越來越臃腫,動輒上千行的代碼量絕對是維護人員的噩夢,因此在MVC基礎上逐漸衍生出來了MVP、MVVM等架構(gòu)模式。
本文是基于OC代碼進行闡述的,使用iOS開發(fā)經(jīng)典的 TableView 列表來分析每個架構(gòu)模式。相信看了這篇文章你會有所領悟。當然一千個人眼中有一千種哈姆雷特,具體在業(yè)務開發(fā)中使用哪種模式需要你自己去衡量。
1.傳統(tǒng)的MVC設計模式
MVC
M: Model 數(shù)據(jù)層,負責網(wǎng)絡數(shù)據(jù)的處理,數(shù)據(jù)持久化存儲和讀取等工作
V: View 視圖層,負責呈現(xiàn)從數(shù)據(jù)層傳遞的數(shù)據(jù)渲染工作,以及與用戶的交互工作
C: Controller控制器,負責連接Model層跟View層,響應View的事件和作為View的代理,以及界面跳轉(zhuǎn)和生命周期的處理等任務
用戶的交互邏輯
用戶點擊 View(視圖) --> 視圖響應事件 -->通過代理傳遞事件到Controller–>發(fā)起網(wǎng)絡請求更新Model—>Model處理完數(shù)據(jù)–>代理或通知給Controller–>改變視圖樣式–>完成
可以看到Controller強引用View與Model,而View與Model是分離的,所以就可以保證Model和View的可測試性和復用性,但是Controller不行,因為Controller是Model和View的中介,所以不能復用,或者說很難復用。
iOS開發(fā)實際使用的MVC架構(gòu)
在實際MVC在我們實際開發(fā)中使用的MVC模式可以看到,View與Controller耦合在一起了。這是由于每一個界面的創(chuàng)建都需要一個Controller,而每一個Controller里面必然會帶一個View,這就導致了C和V的耦合。這種結(jié)構(gòu)確實可以提高開發(fā)效率,但是一旦界面復雜就會造成Controller變得非常臃腫和難以維護。
MVC代碼示例
我們要實現(xiàn)一個簡單的列表頁面,每行cell都一個按鈕,點擊按鈕前面數(shù)字?1操作。
mvcexamp核心代碼:
// 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設計模式
MVPM: Model 數(shù)據(jù)層,負責網(wǎng)絡數(shù)據(jù)的處理,數(shù)據(jù)持久化存儲和讀取等工作
V: View 視圖層,負責呈現(xiàn)從數(shù)據(jù)層傳遞的數(shù)據(jù)渲染工作,以及與用戶的交互,這里把Controller層也合并到視圖層
P: Presenter層,負責視圖需要數(shù)據(jù)的獲取,獲取到數(shù)據(jù)后刷新視圖。響應View的事件和作為View的代理。
可以看到 MVP模式跟原始的MVC模式非常相似,完全實現(xiàn)了View與Model層的分離,而且把業(yè)務邏輯放在了Presenter層中,視圖需要的所有數(shù)據(jù)都從Presenter獲取,而View與 Presenter通過協(xié)議進行事件的傳遞。
用戶的交互邏輯
用戶點擊 View(視圖) --> 視圖響應事件 -->通過代理傳遞事件到Presenter–>發(fā)起網(wǎng)絡請求更新Model–>Model處理完數(shù)據(jù)–>代理或通知給視圖(View或是Controller)–>改變視圖樣式–>完成
MVP代碼示例
項目結(jié)構(gòu)
//DemoProtocal import <Foundation/Foundation.h> @protocol DemoProtocal <NSObject> @optional //用戶點擊按鈕 觸發(fā)事件: UI改變傳值到model數(shù)據(jù)改變 UI --- > Model 點擊cell 按鈕 -(void)didClickCellAddBtnWithIndexPathRow:(NSInteger)index; //model數(shù)據(jù)改變傳值到UI界面刷新 Model --- > UI -(void)reloadUI; @end
我們把所有的代理抽象出來,成為一個Protocal文件。這兩個方法的作用:
-(void)didClickCellAddBtnWithIndexPathRow:(NSInteger)index;:Cell視圖調(diào)用它去Presenter層實現(xiàn)點擊邏輯的處理
-(void)reloadUI;: Presenter調(diào)用它去更新主視圖View或者Controller
//Presenter.h #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> #import "DemoProtocal.h" NS_ASSUME_NONNULL_BEGIN @interface Presenter : NSObject @property (nonatomic, strong,readonly) NSMutableArray *dataArray; @property (nonatomic, weak) id<DemoProtocal>delegate;//協(xié)議,去更新主視圖UI // 更新 TableView UI 根據(jù)需求 - (void)requestDataAndUpdateUI; //更新 cell UI - (void)updateCell:(UITableViewCell*)cell withIndex:(NSInteger)index; @end
dataArray : 視圖需要的數(shù)據(jù)源
- (void)requestDataAndUpdateUI;:主視圖Controller調(diào)用,去更新自己的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 數(shù)據(jù) ? ? [self.presenter updateCell:cell withIndex:indexPath.row]; ? ? cell.selectionStyle = UITableViewCellSelectionStyleNone; ? ? return cell; } #pragma mark - DemoProtocal //Presenter 的代理回調(diào) 數(shù)據(jù)更新了通知View去更新視圖 - (void)reloadUI{ ? ? [self.mainTabelView reloadData]; }
Controller層初始化Presenter,調(diào)用其方法更新自己的UI,可以看到網(wǎng)絡數(shù)據(jù)的獲取,處理都在Presenter中,處理完成后通過協(xié)議回調(diào)給Controller去reload數(shù)據(jù)
//Cell - (void)addBtnDown:(UIButton*)btn{ ? ? NSLog(@"%s",__func__); ? ? if([self.delegate respondsToSelector:@selector(didClickCellAddBtnWithIndexPathRow:)]){ ? ? ? ? [self.delegate didClickCellAddBtnWithIndexPathRow:self.index]; ? ? } }
Cell層點擊事件通過協(xié)議調(diào)用,而這個協(xié)議方法的實現(xiàn)是在Presenter中實現(xiàn)的。
MVP模式也有自身的缺點,所有的用戶操作和更新UI的回調(diào)需要定義,隨著交互越來越復雜,這些定義都要有很大一坨代碼。邏輯過于復雜的情況下,Present本身也會變得臃腫。所以衍生出了MVVM模式。
3.MVVM+RAC設計模式
MVVM
M: Model 數(shù)據(jù)層,負責網(wǎng)絡數(shù)據(jù)的處理,數(shù)據(jù)持久化存儲和讀取等工作
V: View 視圖層,此時的視圖層包括Controller,負責呈現(xiàn)從數(shù)據(jù)層傳遞的數(shù)據(jù)渲染工作,以及與用戶的交互
VM:ViewModel層,負責視圖需要數(shù)據(jù)的獲取,獲取到數(shù)據(jù)后刷新視圖。響應View的事件和作為View的代理等工作。
通過架構(gòu)圖可以看到,MVVM模式跟MVP模式基本類似。主要區(qū)別是在MVP基礎上加入了雙向綁定機制。當被綁定對象某個值的變化時,綁定對象會自動感知,無需被綁定對象主動通知綁定對象??梢允褂肒VO和RAC實現(xiàn)。我們這里采用了RAC的實現(xiàn)方式。關(guān)于RAC如果不熟悉的小伙伴可以點這里,我們這篇文章不在涉及。
MVVM代碼示例
我們這里包括兩層視圖:主視圖Controller以及Cell,分別對應兩層ViewModel:ViewModel和CellViewModel
//ViewModel.h @interface ViewModel : NSObject //發(fā)送數(shù)據(jù)請求的Rac,可以去訂閱獲取 請求結(jié)果 @property (nonatomic,strong,readonly) RACCommand *requestCommand; @property (nonatomic,strong) NSArray *dataArr;//返回子級對象的ViewModel - (CellViewModel *)itemViewModelForIndex:(NSInteger)index; @end
RACCommand *requestCommand:提供供主視圖調(diào)用的命令,調(diào)用它去獲取網(wǎng)絡數(shù)據(jù)
NSArray *dataArr: 提供供主視圖使用的數(shù)據(jù)源,注意這里不能用NSMutableArray,因為NSMutableArray不支持KVO,不能被RACObserve。
- (CellViewModel *)itemViewModelForIndex:(NSInteger)index; 根據(jù)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渲染需要的所有數(shù)據(jù)
RACCommand *addCommand;: 按鈕點擊事件的指令,觸發(fā)后需要在CellViewModel里面做處理。
//controller - (void)iniData{ ? ? self.viewModel = [[ViewModel alloc] init]; ? ? // 發(fā)送請求 ? ? 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 數(shù)據(jù) ? ? cell.cellViewModel = [self.viewModel itemViewModelForIndex:indexPath.row]; ? ? cell.selectionStyle = UITableViewCellSelectionStyleNone; ? ? ? ?? ? ? return cell; }
iniData:初始化ViewModel,并發(fā)送請求命令。這里可以監(jiān)聽這個完成信號,進行刷新視圖操作
cell.cellViewModel = [self.viewModel itemViewModelForIndex:indexPath.row]; 根據(jù)主視圖的ViewModel去獲取Cell的ViewModel,實現(xiàn)cell的數(shù)據(jù)綁定。
//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的數(shù)據(jù)綁定,這邊有個注意Racobserve左邊只有self右邊才有viewModel.titleStr這樣就避Cell重用的問題。
[self.cellViewModel.addCommand execute:nil];:按鈕的點擊方法觸發(fā),事件的處理在CellViewModel中。
總結(jié)
到此這篇關(guān)于iOS架構(gòu)從 MVC、MVP 到 MVVM的文章就介紹到這了,更多相關(guān)淺談MVC的內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!。
相關(guān)文章
iOS框架AVFoundation實現(xiàn)相機拍照、錄制視頻
這篇文章主要為大家詳細介紹了iOS框架AVFoundation實現(xiàn)相機拍照、錄制視頻功能,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-05-05iOS開發(fā)教程之識別圖片中二維碼功能的實現(xiàn)
長按識別二維碼這個功能相信對大家來說都不陌生,最近工作中就遇到了這個需求,所以下面這篇文章主要給大家介紹了關(guān)于利用iOS識別圖片中二維碼的相關(guān)資料,文中通過示例代碼介紹的非常詳細,需要的朋友可以參考下2018-07-07iOS應用設計模式開發(fā)中對簡單工廠和工廠方法模式的運用
這篇文章主要介紹了iOS應用設計模式開發(fā)中對簡單工廠和工廠方法模式的運用,示例代碼為傳統(tǒng)的Objective-C,需要的朋友可以參考下2016-03-03iOS手勢識別的詳細使用方法(拖動,縮放,旋轉(zhuǎn),點擊,手勢依賴,自定義手勢)
這篇文章主要介紹了iOS手勢識別的詳細使用方法(拖動,縮放,旋轉(zhuǎn),點擊,手勢依賴,自定義手勢),具有一定的參考價值,有需要的可以參考一下。2016-11-11使用設計模式中的Singleton單例模式來開發(fā)iOS應用程序
這篇文章主要介紹了使用設計模式中的Singleton單例模式來開發(fā)iOS應用程序的例子,示例代碼為傳統(tǒng)的Objective-C語言,需要的朋友可以參考下2016-03-03iOS多線程應用開發(fā)中使用NSOperation類的基本方法
這篇文章主要介紹了iOS多線程應用開發(fā)中使用NSOperation類的基本方法,代碼基于傳統(tǒng)的Objective-C,需要的朋友可以參考下2015-11-11