
前言
在開(kāi)發(fā)App的時(shí)候,我們的基本目標(biāo)一般有以下幾點(diǎn):
- `可靠性 - App的功能能夠正常使用`
- `健壯性 - 在用戶非正常使用的時(shí)候,app也能夠正常反應(yīng),不要崩潰`
- `效率性 - 啟動(dòng)時(shí)間,耗電,流量,界面反應(yīng)速度在用戶容忍的范圍以內(nèi)`
上面三點(diǎn)是表象層的東西,是大多數(shù)開(kāi)發(fā)者或者團(tuán)隊(duì)會(huì)著重注意的。除了這三點(diǎn),還有一些目標(biāo)是工程方面的也是開(kāi)發(fā)者要注意的:
- `可修改性/可擴(kuò)展性 - 軟件需要迭代,功能不斷完善`
- `容易理解 - 代碼能夠容易理解`
- `可測(cè)試性 - 代碼能夠方便的編寫(xiě)單元測(cè)試和集成測(cè)試`
- `可復(fù)用性 - 不用一次又一次造輪子`
基于這些設(shè)計(jì)目標(biāo)和理念,軟件設(shè)計(jì)領(lǐng)域又有了設(shè)計(jì)模式。MVC/MVVM都是就是設(shè)計(jì)模式的一種。
在MVC的架構(gòu)中,Model持有數(shù)據(jù),View顯示與用戶交互的界面,而ViewController調(diào)解Model和View之間的交互。
現(xiàn)在,MVC 依然是目前主流客戶端編程框架,但同時(shí)它也被調(diào)侃成Massive View Controller(重量級(jí)視圖控制器),
開(kāi)發(fā)者在開(kāi)發(fā)中無(wú)可避免被下面幾個(gè)問(wèn)題所困擾:
- 厚重的ViewController
- 遺失的網(wǎng)絡(luò)邏輯(無(wú)立足之地)
- 較差的可測(cè)試性
而MVVM這種新的代碼組織方式就可以解決這些問(wèn)題,本文就MVVM的架構(gòu)設(shè)計(jì)做個(gè)簡(jiǎn)單的個(gè)人總結(jié)。
MVVM概述
從圖中我們可以看到MVVM的關(guān)系基本是:View <-> C <-> ViewModel <-> Model,
嚴(yán)格來(lái)說(shuō)MVVM其實(shí)是MVCVM。Controller夾在View和ViewModel之間做的其中一個(gè)主要事情就是將View和ViewModel進(jìn)行綁定. 在邏輯上,Controller知道應(yīng)當(dāng)展示哪個(gè)View,Controller也知道應(yīng)當(dāng)使用哪個(gè)ViewModel, 然而View和ViewModel它們之間是互相不知道的,所以Controller就負(fù)責(zé)控制他們的綁定關(guān)系。
MVVM 一種可以很好地解決Massive View Controller問(wèn)題的辦法
就是將 Controller 中的展示邏輯抽取出來(lái),放置到一個(gè)專門的地方,
而這個(gè)地方就是 viewModel 。MVVM衍生于MVC,是對(duì) MVC 的一種演進(jìn),
它促進(jìn)了 UI 代碼與業(yè)務(wù)邏輯的分離。
它正式規(guī)范了視圖和控制器緊耦合的性質(zhì),并引入新的組件。他們之間的結(jié)構(gòu)關(guān)系如下:
不難看出,MVVM是對(duì)MVC的擴(kuò)展,所以MVVM可以完美的兼容MVC。
對(duì)于一個(gè)界面來(lái)說(shuō),有時(shí)候View和ViewModel往往不止一個(gè),MVVM也可以組合使用:

MVVM 的基本概念
- 在MVVM 中,view 和 view controller正式聯(lián)系在一起,我們把它們視為一個(gè)組件,
Controller可以當(dāng)作一個(gè)重量級(jí)的View(負(fù)責(zé)界面切換和處理各類系統(tǒng)事件)。
- view 和 view controller 都不能直接引用model,而是引用視圖模型(viewModel)
- viewModel 是一個(gè)放置用戶輸入驗(yàn)證邏輯,視圖顯示邏輯,發(fā)起網(wǎng)絡(luò)請(qǐng)求和其他代碼的地方,
它的職責(zé)之一就是作為一個(gè)表現(xiàn)視圖顯示自身所需數(shù)據(jù)的靜態(tài)模型;但它也有收集, 解釋和轉(zhuǎn)換那些數(shù)據(jù)的責(zé)任。
它是從 MVC 的 controller 中抽取出來(lái)的展示邏輯,負(fù)責(zé)從 model中獲取 view 所需的數(shù)據(jù),
轉(zhuǎn)換成 view可以展示的數(shù)據(jù),并暴露公開(kāi)的屬性和命令供 view 進(jìn)行綁定。
- 使用MVVM會(huì)輕微的增加代碼量,但總體上減少了代碼的復(fù)雜性。
MVVM 的注意事項(xiàng)
- viewController 盡量不涉及業(yè)務(wù)邏輯,讓 viewModel 去做這些事情。
- viewController 只是一個(gè)中間人,接收 view 的事件、調(diào)用 viewModel 的方法、響應(yīng) viewModel 的變化。
一方面負(fù)責(zé)View和ViewModel之間的綁定,另一方面也負(fù)責(zé)常規(guī)的UI邏輯處理。
- view 引用viewModel ,但反過(guò)來(lái)不行(即不要在viewModel中引入#import UIKit.h,
任何視圖本身的引用都不應(yīng)該放在viewModel中)(PS:基本要求,必須滿足)
- viewModel 引用model,但反過(guò)來(lái)不行
- viewModel 絕對(duì)不能包含視圖 view(UIKit.h),不然就跟 view 產(chǎn)生了耦合,不方便復(fù)用和測(cè)試。
- viewModel之間可以有依賴。
- viewModel避免過(guò)于臃腫,否則重蹈Controller的覆轍,變得難以維護(hù)。
關(guān)于MVVM Without ReactiveCocoa
為了讓View和ViewModel之間能夠有比較松散的綁定關(guān)系,于是我們使用ReactiveCocoa,
KVO,Notification,block,delegate和target-action都可以用來(lái)做數(shù)據(jù)通信,
從而來(lái)實(shí)現(xiàn)綁定,但都不如ReactiveCocoa提供的RACSignal來(lái)的優(yōu)雅,
使用函數(shù)響應(yīng)式框架能更好的實(shí)現(xiàn)數(shù)據(jù)和視圖的雙向綁定(ViewModel的數(shù)據(jù)可以顯示到View上,
View上的操作同樣會(huì)引起ViewModel的變化),降低了ViewModel和View的耦合度。
如果不用ReactiveCocoa,綁定關(guān)系可能就做不到那么松散那么好,但并不影響它還是MVVM。
MVVM的關(guān)鍵是要有ViewModel。而不是ReactiveCocoa、RXSwift或RXJava等。
而在現(xiàn)實(shí)中我傾向于使用 block而不是 KVO,因?yàn)镵VO的代碼量太大了,block則簡(jiǎn)潔的多。
ReactiveCocoa或RXSwift通過(guò)這兩個(gè)框架可以實(shí)現(xiàn)ViewModel和View的雙向綁定,
但同樣會(huì)存在幾個(gè)比較重大的問(wèn)題。 首先,ReactiveCocoa或RXSwift的學(xué)習(xí)成本很高;
其次,
數(shù)據(jù)綁定使得 Bug 很難被調(diào)試,當(dāng)界面出現(xiàn)異常,可能是View的問(wèn)題,也可能是數(shù)據(jù)ViewModel的問(wèn)題。 而數(shù)據(jù)綁定會(huì)使一個(gè)位置的bug傳遞到其他位置,難以定位。
MVVM Without ReactiveCocoa的一個(gè)應(yīng)用實(shí)例
下面的內(nèi)容源自這篇文章,我覺(jué)得舉例很得到就引用過(guò)來(lái)了:原文在這里
-
效果圖
-
登錄頁(yè)面邏輯分析圖
ViewModel的設(shè)計(jì)
/// 登錄界面的視圖模型 -- VM
@interface SULoginViewModel1 : NSObject
/// 手機(jī)號(hào)
@property (nonatomic, readwrite, copy) NSString *mobilePhone;
/// 驗(yàn)證碼
@property (nonatomic, readwrite, copy) NSString *verifyCode;
/// 登錄按鈕的點(diǎn)擊狀態(tài)
@property (nonatomic, readonly, assign) BOOL validLogin;
/// 用戶頭像
@property (nonatomic, readonly, copy) NSString *avatarUrlString;
/// 用戶登錄 為了減少View對(duì)viewModel的狀態(tài)的監(jiān)聽(tīng) 這里采用block回調(diào)來(lái)減少狀態(tài)的處理
- (void)loginSuccess:(void(^)(id json))success
failure:(void (^)(NSError *error))failure;
@end
很明顯viewModel僅僅只暴漏了視圖控制器所必需的最小量的信息,設(shè)置readonly屬性很有必要,同時(shí),視圖控制器C實(shí)際上并不在乎 viewModel是如何獲得這些信息的。切記:ViewModel千萬(wàn)不要主動(dòng)對(duì)視圖控制器C以任何形式直接起作用或直接通告其變化,而是等待視圖控制器C來(lái)主動(dòng)獲取。
想必大家可能對(duì)下面的代碼存在疑惑,原因可能是:不是說(shuō)好的 View綁定ViewModel的呢?綁定呢?監(jiān)聽(tīng)呢?....
/// 用戶登錄 為了減少View對(duì)viewModel的狀態(tài)的監(jiān)聽(tīng) 這里采用block回調(diào)來(lái)減少狀態(tài)的處理
- (void)loginSuccess:(void(^)(id json))success
failure:(void (^)(NSError *error))failure;
對(duì)方不想和筆者說(shuō)話并向筆者扔了一個(gè)API設(shè)計(jì)
/// 是否正在執(zhí)行
@property (nonatomic, readonly, assign) BOOL executing;
/// 請(qǐng)求失敗的信息
@property (nonatomic, readonly, strong) NSError *error;
/// 請(qǐng)求成功的數(shù)據(jù)
@property (nonatomic, readonly, strong) id responseObject;
/// 調(diào)起登錄
- (void) login;
這樣設(shè)計(jì)其實(shí)也合理的,ViewController的登錄按鈕被點(diǎn)擊時(shí),調(diào)用viewModel上的login方法,同時(shí)ViewController通過(guò)KVO的方法監(jiān)聽(tīng)executing、error、responseObject的屬性即可,代碼大致如下:
_KVOController = [FBKVOController controllerWithObserver:self];
@weakify(self);
/// binding self.viewModel.executing
[_KVOController mh_observe:self.viewModel keyPath:@"executing" block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
/// 根據(jù)executing的值,控制 HUD的顯示和隱藏
if([change[NSKeyValueChangeNewKey] boolValue])
{
[MBProgressHUD mh_showProgressHUD:@"Loading..."];
}else{
[MBProgressHUD mh_hideHUD];
}
}];
/// binding self.viewModel.responseObject
[_KVOController mh_observe:self.viewModel keyPath:@"responseObject" block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
/// 成功的數(shù)據(jù)處理
}];
/// binding self.viewModel.error
[_KVOController mh_observe:self.viewModel keyPath:@"error" block:^(id _Nullable observer, id _Nonnull object, NSDictionary<NSString *,id> * _Nonnull change) {
@strongify(self);
/// 失敗的數(shù)據(jù)處理
}];
筆者不想和你說(shuō)話并向你扔了一個(gè)問(wèn)題思考。上面??一個(gè)登陸(login)操作,我們就要編寫(xiě)這么多代碼,試想如果再多一個(gè)操作呢?再多兩個(gè)操作呢?.... 如果不用block回調(diào),不管你們會(huì)不會(huì),總之,我會(huì)。下面??再看看利用block的回調(diào)實(shí)現(xiàn),你們就會(huì)解惑,釋?xiě)蚜?,起碼好受點(diǎn)。
[MBProgressHUD mh_showProgressHUD:@"Loading..."];
@weakify(self);
[self.viewModel loginSuccess:^(id json) {
@strongify(self);
[MBProgressHUD mh_hideHUD];
/// 成功的數(shù)據(jù)處理
} failure:^(NSError *error) {
/// 失敗的數(shù)據(jù)處理
}];
- ViewController(視圖控制器)在此中的作用
1、視圖控制器從 viewModel獲取的數(shù)據(jù)將用來(lái):
當(dāng)validLogin的值發(fā)生變化時(shí),觸發(fā)登錄按鈕的enabled的屬性。
監(jiān)聽(tīng)avatarUrlString的變化,來(lái)更新視圖控制器的頭像的UIImageView。
2、視圖控制器對(duì) viewModel 起如下作用:
每當(dāng) UITextField 中的文本發(fā)生變化, 更新 viewModel上的 readwrite屬性 mobilePhone或者verifyCode
登錄按鈕被點(diǎn)擊時(shí),調(diào)用viewModel上的loginSuccess:failure方法。
3、視圖控制器不要做的事
發(fā)起登錄的網(wǎng)絡(luò)請(qǐng)求
判定登錄按鈕的有效性
來(lái)獲取頭像的地址(PS:有可能從本地?cái)?shù)據(jù)庫(kù)獲取,也有可能通過(guò)網(wǎng)絡(luò)請(qǐng)求來(lái)獲?。?...
請(qǐng)?jiān)俅巫⒁庖晥D控制器總的責(zé)任是處理viewModel中的變化。
商品首頁(yè)界面的實(shí)踐
- ViewModel的設(shè)計(jì)
/// 商品首頁(yè)的視圖模型 -- VM
@interface SUGoodsViewModel1 : NSObject
/// banners
@property (nonatomic, readonly, copy) NSArray <NSString *> *banners;
/// The data source of table view.
@property (nonatomic, readwrite, strong) NSMutableArray *dataSource;
/// load banners data
- (void)loadBannerData:(void (^)(id responseObject))success
failure:(void (^)(NSError *))failure;
/**
* 加載網(wǎng)絡(luò)數(shù)據(jù) 通過(guò)block回調(diào)減輕view 對(duì) viewModel 的狀態(tài)的監(jiān)聽(tīng)
@param success 成功的回調(diào)
@param failure 失敗的回調(diào)
@param configFooter 底部刷新控件的狀態(tài) lastPage = YES ,底部刷新控件hidden,反之,show
*/
- (void)loadData:(void(^)(id json))success
failure:(void(^)(NSError *error))failure
configFooter:(void(^)(BOOL isLastPage))configFooter;
@end
-
ViewController(視圖控制器)
視圖控制器通過(guò)調(diào)用viewModel的loadBannerData:failure:和loadData:failure:configFooter:來(lái)獲取商品首頁(yè)的廣告數(shù)據(jù)(SUBanner)以及商品數(shù)據(jù)(SUGoods)。視圖控制器通過(guò)使用viewModel上的banners和dataSource數(shù)組中的對(duì)象來(lái)配置表格視圖(tableView)的tableViewHeader和cell。通常我們會(huì)期待展現(xiàn)dataSource的是數(shù)據(jù)-模型對(duì)象。同時(shí)你可能已經(jīng)對(duì)其感到奇怪, 因?yàn)槲覀冊(cè)噲D通過(guò)MVVM模式不暴漏數(shù)據(jù)-模型對(duì)象。 (前面提到過(guò)的)。
假設(shè)我們暴露數(shù)據(jù)-模型(SUGoods),那就分析如下:
我們不瞎,明顯從上圖??可以看出視圖 SUGoodsCell直接引用了模型SUGoods,這就有悖了MVVM的初衷:view和 view controller 都不能直接引用model,而是引用視圖模型(viewModel)
-
子ViewModel
我們必須明確:viewModel不必在屏幕上顯示所有東西。在工作中如果遇到量級(jí)非常重的控制器,可以針對(duì)實(shí)際的業(yè)務(wù),將一組業(yè)務(wù)邏輯相關(guān)的代碼抽取到一個(gè)獨(dú)立的視圖模型中處理。你可用
子viewModel來(lái)代表屏幕上更小的、更潛在的被封裝的部分。
一般來(lái)說(shuō),viewController可以帶一個(gè)viewModel,那如果出現(xiàn)Cell時(shí)怎么辦,Cell里又包含了按鈕,按鈕又需要數(shù)據(jù)請(qǐng)求又怎么處理?這些都是比較常見(jiàn)的場(chǎng)景,也可以通過(guò)MVVM來(lái)解決。
我們知道viewModel的職責(zé)是為view提供數(shù)據(jù)支持,Cell也是一個(gè)View,那么為Cell配備一個(gè)viewModel不就可以了么。所以相對(duì)于ViewController的ViewModel來(lái)說(shuō),Cell上配備的viewModel就是子viewModel。
你不總是需要子viewModel。 比如,筆者可能用表格tableHeaderView視圖來(lái)渲染簡(jiǎn)單的頁(yè)面展示。它不是個(gè)可重用的組件,所以筆者可能僅將我們已經(jīng)給視圖控制器用過(guò)的相同的viewModel傳給那個(gè)自定義的header視圖。它會(huì)用到viewModel中它需要的信息,而無(wú)視余下的部分。
針對(duì)上面??發(fā)現(xiàn)的問(wèn)題,筆者優(yōu)化如下:
從上面??可知,dataSource是一個(gè)里面裝著SUGoodsItemViewModel的對(duì)象數(shù)組,在表格視圖中的 tableView: cellForRowAtIndexPath:方法中,將會(huì)從視圖控制器的viewModel的dataSource中通過(guò)正確的索引獲取到子viewModel, 并把它賦值給 cell上的 viewModel屬性。
想必大家還有一個(gè)疑惑,數(shù)據(jù)-模型(SUGoods)是否要通過(guò)屬性的方式暴露在子視圖模型(SUGoodsItemViewModel)的.h文件中?
我們假設(shè)要通過(guò)SUGoodsItemViewModel來(lái)提供給SUGoodsCell展示下面??的界面的數(shù)據(jù):
商品模型(SUGoods)的數(shù)據(jù)結(jié)構(gòu)如下:
/** 商品運(yùn)費(fèi)類型 */
typedef NS_ENUM(NSUInteger, SUGoodsExpressType) {
SUGoodsExpressTypeFree = 0, // 包郵
SUGoodsExpressTypeValue = 1, // 運(yùn)費(fèi)
SUGoodsExpressTypeFeeding = 2,// 待議
};
@interface SUGoods : SUModel
/// === 商品相關(guān)的屬性 ===
....
/// === 商品中的用戶相關(guān)的信息 ===
/// 用戶ID
@property (nonatomic, readwrite, copy) NSString * userId;
/// 用戶頭像
@property (nonatomic, readwrite, copy) NSString * avatar;
/// 用戶昵稱:
@property (nonatomic, readwrite, copy) NSString * nickName;
/// 是否芝麻認(rèn)證
@property (nonatomic, readwrite, assign) BOOL iszm;
@end
假設(shè)我們將數(shù)據(jù)-模型通過(guò)屬性暴露在子視圖模型的.h中,筆者將設(shè)計(jì)SUGoodsItemViewModel.h/m大致代碼如下??:
/// SUGoodsItemViewModel.h
/// 數(shù)據(jù)-模型(SUGoods)以屬性的方式暴露
@interface SUGoodsItemViewModel : NSObject
/// 商品模型
@property (nonatomic, readonly, strong) SUGoods *goods;
/// 用戶ID:101921
@property (nonatomic, readonly, copy) NSString * userId;
/// 初始化
- (instancetype)initWithGoods:(SUGoods *)goods;
@end
/// SUGoodsItemViewModel.m
@interface SUGoodsItemViewModel ()
/// 商品模型
@property (nonatomic, readwrite, strong) SUGoods *goods;
/// 用戶id
@property (nonatomic, readwrite, copy) NSString *userId;
@end
@implementation SUGoodsItemViewModel
- (instancetype)initWithGoods:(SUGoods *)goods
{
self = [super init];
if (self) {
self.goods = goods;
self.userId = [NSString stringWithFormat:@"用戶ID:%@",goods.userId]
}
return self;
}
筆者將設(shè)計(jì)SUGoodsCell.m大致代碼如下??:
/// SUGoodsCell.m
- (void)bindViewModel:(SUGoodsItemViewModel *)viewModel
{
self.viewModel = viewModel;
/// 頭像
[MHWebImageTool setImageWithURL:viewModel.goods.avatar placeholderImage:placeholderUserIcon() imageView:self.userHeadImageView];
/// 昵稱
self.userNameLabel.text = viewModel.goods.nickName;
/// 芝麻認(rèn)證
self.realNameIcon.hidden = !viewModel.goods.iszm;
/// 用戶ID
self.userIdLabel.text = viewModel.userId;
}
既然通過(guò)屬性暴露了數(shù)據(jù)-模型(SUGoods)了,為何還要暴露一個(gè)userId的屬性?有必要嗎?很有必要?。?!
上面已經(jīng)提到過(guò)ViewModel 提供額外數(shù)據(jù)轉(zhuǎn)換的屬性, 或?yàn)樘囟ǖ囊晥D計(jì)算數(shù)據(jù)。顯然我們完全可以不暴露userId,僅僅只要我們?cè)赟UGoodsCell.m中這樣寫(xiě)即可,根本無(wú)傷大雅是吧。
/// SUGoodsCell.m
- (void)bindViewModel:(SUGoodsItemViewModel *)viewModel
{
self.viewModel = viewModel;
/// 頭像
[MHWebImageTool setImageWithURL:viewModel.goods.avatar placeholderImage:placeholderUserIcon() imageView:self.userHeadImageView];
/// 昵稱
self.userNameLabel.text = viewModel.goods.nickName;
/// 芝麻認(rèn)證
self.realNameIcon.hidden = !viewModel.goods.iszm;
/// 用戶ID
self.userIdLabel.text =[NSString stringWithFormat:@"用戶ID:%@",viewModel.goods.userId] ;
}
對(duì)此,筆者只能微微一笑很傾城了。因?yàn)檫@個(gè)數(shù)據(jù)的屬性過(guò)于簡(jiǎn)單,僅僅只是數(shù)據(jù)的拼接,看不出viewModel的作用和強(qiáng)大。詳情見(jiàn)下面??商品運(yùn)費(fèi)Label的顯示邏輯:
/// 郵費(fèi)情況
NSString *freightExplain = nil;
SUGoodsExpressType expressType = goods.expressType;
if (expressType==SUGoodsExpressTypeFree) {
// 包郵
freightExplain = @"包郵";
}else if(expressType == SUGoodsExpressTypeValue){
// 指定運(yùn)費(fèi)
NSString *extralFee = [NSString stringWithFormat:@"運(yùn)費(fèi) ¥%@",goods.expressFee];
freightExplain = extralFee;
}else if (expressType == SUGoodsExpressTypeFeeding){
freightExplain = @"運(yùn)費(fèi)待議";
}
self.freightExplain = freightExplain;
至此,筆者相信大家都會(huì)把上面??這段代碼寫(xiě)在ViewModel中,通過(guò)暴露一個(gè)只讀(readonly)的freightExplain屬性供cell獲取展示,而不是Cell中編寫(xiě)這段又臭又長(zhǎng)的邏輯代碼。
基于 MVVM 的更瘦身的架構(gòu)設(shè)計(jì)方式
MVVM的出現(xiàn)主要是為了解決在開(kāi)發(fā)過(guò)程中Controller越來(lái)越龐大的問(wèn)題,變得難以維護(hù),
所以MVVM把數(shù)據(jù)加工的任務(wù)從Controller中解放了出來(lái),使得Controller只需要專注于數(shù)據(jù)調(diào)配的工作,
ViewModel則去負(fù)責(zé)數(shù)據(jù)加工并通過(guò)通知機(jī)制讓View響應(yīng)ViewModel的改變。
MVVM是基于胖Model的架構(gòu)思路建立的,然后在胖Model中拆出兩部分:Model和ViewModel。
ViewModel本質(zhì)上算是Model層(因?yàn)槭桥諱odel里面分出來(lái)的一部分),所以 ViewModel里面不能包含任何 UIKit的內(nèi)容。
而且View并不一定適合直接持有ViewModel,因?yàn)閂iewModel有可能并不是只服務(wù)于特定的一個(gè)View,
如果我們對(duì)于單個(gè)復(fù)雜View設(shè)計(jì)一個(gè) ViewModel 是可以讓該 View 持有該 ViewModel的。

如圖我們?cè)O(shè)計(jì)了一個(gè)基于 MVVM 的更瘦身的架構(gòu)設(shè),這個(gè)架構(gòu)中:

* View - 用來(lái)呈現(xiàn)用戶界面
* ViewManger - 用來(lái)處理View的常規(guī)事件,負(fù)責(zé)管理View
* Controller - 負(fù)責(zé)ViewManger和ViewModel之間的綁定,負(fù)責(zé)控制器本身的生命周期。
* ViewModel - 存放各種業(yè)務(wù)邏輯和網(wǎng)絡(luò)請(qǐng)求,不能存在 UIKit 有關(guān)的東西。
* Model - 用來(lái)呈現(xiàn)數(shù)據(jù)
這種設(shè)計(jì)的目的是保持View和Model的高度純潔,提高可擴(kuò)展性和復(fù)用度。
在日常開(kāi)發(fā)中,ViewModel是為了拆分Controller業(yè)務(wù)邏輯而存在的,
所以ViewModel需要提供公共的服務(wù)接口,以便為Controller提供數(shù)據(jù)。
而ViewManger的作用相當(dāng)于一個(gè)小管家,幫助Controller來(lái)分別管理每個(gè)subView,ViewManger負(fù)責(zé)接管來(lái)自View的事件,
也負(fù)責(zé)接收來(lái)自Controller的模型數(shù)據(jù),
而View進(jìn)行自己所負(fù)責(zé)的視圖數(shù)據(jù)綁定工作。
Controller則是最后的大家長(zhǎng),負(fù)責(zé)將ViewModel和ViewManger進(jìn)行綁定,
進(jìn)行數(shù)據(jù)轉(zhuǎn)發(fā)工作。把合適的數(shù)據(jù)模型分發(fā)給合適的視圖管理者。
這樣的架構(gòu)設(shè)計(jì),就像一條生產(chǎn)線,ViewModel進(jìn)行數(shù)據(jù)的采集和加工,Controller則進(jìn)行數(shù)據(jù)的裝配和轉(zhuǎn)發(fā)工作,ViewManger進(jìn)行接收轉(zhuǎn)發(fā)分配來(lái)的數(shù)據(jù),從而進(jìn)行負(fù)責(zé)View的展示工作和管理View的事件。這樣,不管哪個(gè)環(huán)節(jié),都是可以更換的,同時(shí)也提高了復(fù)用性。
總結(jié)
iOS App是一個(gè)麻雀雖小,五臟俱全的軟件。良好的架構(gòu)和設(shè)計(jì)能夠讓代碼容易理解和維護(hù),并且不易出錯(cuò)。但是本文可能也存在錯(cuò)誤之處,或者不足之處,希望大家看到有問(wèn)題的地方在下方留言一起談?wù)搶W(xué)習(xí),后續(xù)可能會(huì)持續(xù)更新更正本文。
參考文章:
https://github.com/lovemo/MVVMFramework/tree/master/source
MVVM與Controller瘦身實(shí)踐
iOS 關(guān)于MVC和MVVM設(shè)計(jì)模式的那些事
iOS 關(guān)于MVVM Without ReactiveCocoa設(shè)計(jì)模式的那些事