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