做一個(gè)用戶登錄中心池颈,那么首先。钓丰。躯砰。
- 使用何種設(shè)計(jì)模式 ,MVC 携丁,MVP 琢歇,MVVM?
為什么第一個(gè)問題要關(guān)注選擇何種設(shè)計(jì)模式 ,因?yàn)槿绻悴魂P(guān)心設(shè)計(jì)模式梦鉴,慢慢的你會發(fā)現(xiàn)UserCenterViewController 里面的代碼會滿滿積累李茫,最終變成一個(gè)Massive ViewController ,想快速的修bug 或者增加功能變得異常麻煩。
今天談?wù)摰脑O(shè)計(jì)模式
- MVC
- MVP
- MVVM
三者的共同點(diǎn)表展在Model 和 View
Models : 負(fù)責(zé)數(shù)據(jù)的操作。
View : UI 展示,沒什么好說的枚驻。
三者的差異主要體現(xiàn)在粘合View和Model的方式:
1.Controller接收View的操作事件首妖,根據(jù)事件不同,或者調(diào)用Model的接口進(jìn)行數(shù)據(jù)操作塔次,或者進(jìn)行View的跳轉(zhuǎn)方篮,從而也意味著一個(gè)Controller可以對應(yīng)多個(gè)View。Controller對View的實(shí)現(xiàn)不太關(guān)心励负,只會被動地接收藕溅,Model的數(shù)據(jù)變更不通過Controller直接通知View,通常View采用觀察者模式監(jiān)聽Model的變化继榆。
2.Presenter巾表,與Controller一樣,接收View的命令略吨,對Model進(jìn)行操作集币;與Controller不同的是Presenter會反作用于View,Model的變更通知首先被Presenter獲得翠忠,然后Presenter再去更新View鞠苟。一個(gè)Presenter只對應(yīng)于一個(gè)View。根據(jù)Presenter和View對邏輯代碼分擔(dān)的程度不同秽之,這種模式又有兩種情況:Passive View和Supervisor Controller当娱。
3.ViewModel,注意這里的“Model”指的是View的Model考榨,跟上面那個(gè)Model不是一回事跨细。所謂View的Model就是包含View的一些數(shù)據(jù)屬性和操作的這么一個(gè)東東,這種模式的關(guān)鍵技術(shù)就是數(shù)據(jù)綁定(data binding)河质,View的變化會直接影響ViewModel冀惭,ViewModel的變化或者內(nèi)容也會直接體現(xiàn)在View上震叙。這種模式實(shí)際上是框架替應(yīng)用開發(fā)者做了一些工作,開發(fā)者只需要較少的代碼就能實(shí)現(xiàn)比較復(fù)雜的交互云头。
Coding time
MVC
Model:
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
@property (nonatomic,copy) NSString *avatarUrl;
@end
Controller & View:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) AccountInfo *accountInfo;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo
{
self = [super init];
if (self)
{
self.accountInfo = accountInfo;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
[self.renameButton addTarget:self action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
- (void)renameAction
{
self.accountInfo.userName = @"vedon";
self.userDescLabel.text = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile];
}
@end
調(diào)用方法:
AccountInfo *accountInfo = [AccountInfo new];
UserCenterViewController *userCenterController = [[UserCenterViewController alloc] initWithAccountInfo:accountInfo];
[self presentViewController:userCenterController animated:YES completion:nil];
一開始所有的controller 都是如此純潔捐友,隨著需求迭代,你懂的溃槐。匣砖。。很早之前就有一篇文章提出了這個(gè)問題昏滴。MVC 很容易寫出很臃腫的Controller 猴鲫,但是它有一個(gè)很明顯的優(yōu)點(diǎn),開發(fā)效率高谣殊!
如何避臃腫拂共?
- 盡量避免在Controller 的view上添加過多的view.可以把有復(fù)雜層次結(jié)構(gòu)的view抽離成一層contentView,提高復(fù)用率姻几。
- 在controller 中寫業(yè)務(wù)邏輯代碼宜狐,要注意抽離成單獨(dú)的小模塊,形成復(fù)用模塊蛇捌。
- 動畫代碼抽離到單獨(dú)的模塊抚恒。
提高代碼的復(fù)用率,帶來的好處可能是意想不到的络拌。在開發(fā)夸克瀏覽器的時(shí)候俭驮,云同步底層是直接拿UC瀏覽器的,不需要任何改動就可以使用了春贸。
MVP
在MVP 里面,UIViewController 實(shí)際上當(dāng)View來使用了混萝,Presenters充當(dāng)了Controller的角色。這種方式提高了可測試性萍恕,但是付出的代價(jià)是開發(fā)效率降低了逸嘀。
Model:
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
@end
Presenter:
@interface UserCenterPresenter : NSObject<UserCenterViewProtocol>
@property (nonatomic,weak) id<UserCenterViewProtocol> view;
@property (nonatomic,strong) AccountInfo *accountInfo;
@end
@implementation Presenter
- (void)showUserInfo
{
NSString *desc = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile]
[self.view showUserInfoWithDesc:desc];
}
- (void)renameAction
{
self.accountInfo.userName = @"vedon";
[self showUserInfo];
}
@end
Protocol:
@protocol UserCenterViewProtocol <NSObject>
- (void)showUserInfoWithDesc:(NSString *)desc;
@end
View/Controller:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) UserCenterPresenter *presenter;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo presenter:(UserCenterPresenter *)presenter
{
self = [super init];
if (self)
{
self.presenter = presenter;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
[self.renameButton addTarget:self.presenter action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
#pragma mark - UserCenterViewProtocol
- (void)showUserInfoWithDesc:(NSString *)desc
{
self.userDescLabel.text = desc;
}
@end
調(diào)用方法:
AccountInfo *accountInfo = [AccountInfo new];
UserCenterPresenter *presenter = [UserCenterPresenter new];
presenter.accountInfo = accountInfo;
UserCenterViewController *userCenterController = [[UserCenterViewController alloc] initWithAccountInfo:accountInfo presenter:presenter];
presenter.view = userCenterController;
[self presentViewController:userCenterController animated:YES completion:nil];
MVP從更早的MVC框架演變過來的一種框架,與MVC有一定的相似性雄坪。MVP框架由3部分組成:View負(fù)責(zé)顯示厘熟,Presenter負(fù)責(zé)邏輯處理(一個(gè)Presenter只對應(yīng)于一個(gè)View或者是一類view),Model提供數(shù)據(jù)维哈。MVP與MVC之間最主要的區(qū)別在控制層上绳姨,在MVP框架中,View與Model并不直接交互阔挠,所有的交互放在Presenter中飘庄;而在MVC里,View與Model會直接產(chǎn)生一定的交互购撼。MVP的Presenter是框架的控制者跪削,承擔(dān)了大量的邏輯操作谴仙,而MVC的Controller更多時(shí)候承擔(dān)一種轉(zhuǎn)發(fā)的作用。因此在App中引入MVP的原因碾盐,是為了將此前在Controller中包含的大量邏輯操作放到控制層中晃跺,避免Controller的臃腫。
現(xiàn)在假如我們通過上面的方式解決了部分C太重的問題毫玖,我們看上圖Presenter涉及到的調(diào)用非常復(fù)雜掀虎,這幾個(gè)交互可以說是業(yè)務(wù)代碼的bug聚集地,單向的數(shù)據(jù)流能規(guī)避很多問題付枫。
MVVM
Model :
@interface AccountInfo : NSObject
@property (nonatomic,copy) NSString *userName;
@property (nonatomic,copy) NSString *mobile;
Protocol:
@protocol UserCenterViewModelProtocol <NSObject>
- (void)renameAction;
@end
ViewModel:
typedef void (^UserInfoDidChangeCallback)(NSString *desc);
@interface UserCenterViewModel : NSObject<UserCenterViewProtocol>
@property (nonatomic,weak) id<UserCenterViewProtocol> view;
@property (nonatomic,strong) AccountInfo *accountInfo;
@property (nonatomic,strong) UserInfoDidChangeCallback userInfoDidChangeCallback;
- (void)showUserInfo;
@end
@implementation UserCenterViewModel
- (void) renameAction
{
self.accountInfo.userName = @"vedon";
if(self.userInfoDidChangeCallback)
{
NSString *desc = [NSString stringWithFormat:@"%@ %@",self.accountInfo.userName,self.accountInfo.mobile]
self. userInfoDidChangeCallback(desc)
}
}
@end
Controller:
@interface UserCenterViewController ()
@property (nonatomic,strong) UILabel *userDescLabel;
@property (nonatomic,strong) UIButtom *renameButton;
@property (nonatomic,strong) UserCenterViewModel *viewModel;
@end
@implementation UserCenterViewController
- (instancetype)initWithAccountInfo:(AccountInfo *)accountInfo viewModel:(UserCenterViewModel *)viewModel
{
self = [super init];
if (self)
{
self.viewModel = viewModel;
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.userDescLabel];
self.viewModel. userInfoDidChangeCallback = ^(NSString *desc){
self.userDescLabel.text = desc;
};
[self.renameButton addTarget:self.viewModel action:@selector(renameAction) forControlEvents:UIControlEventTouchUpInside] ;
}
@end
MVP 和MVVM 非常相似烹玉!事實(shí)上,MVVM的View要比MVP中的View承擔(dān)的責(zé)任多阐滩。因?yàn)榍罢咄ㄟ^ViewModel的設(shè)置綁定來更新狀態(tài)二打,而后者只監(jiān)聽Presenter的事件但并不會對自己有什么更新。MVVM 和MVP帶來的好處非常明顯掂榔,它非常清晰的拆分開UI層和數(shù)據(jù)層 继效,使單元測試更加容易。在復(fù)雜的表示邏輯下装获,優(yōu)點(diǎn)會越發(fā)明顯莲趣!
對于ViewModel 的角色定位一定要清晰,它的存在是為了抽離ViewController 中的展示業(yè)務(wù)邏輯饱溢,視圖操作邏輯依然是在Controller 實(shí)現(xiàn)。那么在它里面就不應(yīng)該出現(xiàn)Push/Present 以及其他UIKit 的東西走芋。而展示業(yè)務(wù)邏輯是什么绩郎?就是負(fù)責(zé)將數(shù)據(jù)業(yè)務(wù)層提供的數(shù)據(jù)轉(zhuǎn)化為界面展示所需要,Model的信息可能并不能正確體現(xiàn)View狀態(tài)翁逞,所以需要經(jīng)過ViewModel的轉(zhuǎn)化肋杖。
MMVM 的關(guān)鍵在與數(shù)據(jù)綁定,查了一下挖函,已有開源框架在objc 的基礎(chǔ)上實(shí)現(xiàn)了一套數(shù)據(jù)綁定的框架状植。
題外話: 業(yè)務(wù)拆分
無論采取何種模式開發(fā),業(yè)務(wù)邏輯就在那里怨喘,不多不少津畸。ViewController 使用category 的形式進(jìn)行拆分。這樣一來必怜,Massive View Controller
此外肉拓,UI 組件設(shè)置樣式的代碼也可以通過 Category 的方法拆出去了。例如 :UILabel + PriceStyle 這個(gè) Category 里梳庆,UIButton 也是一樣的處理方式暖途。在使用的時(shí)候卑惜,直接通過 Category 里的方法獲取實(shí)例即可.
張三豐教無忌太極劍法,教完了卻讓他忘掉驻售,這是為什么呢
對話好像是這樣:“還記得嗎露久?”
“全都記得∑劾酰”
“現(xiàn)在呢毫痕?”
“已經(jīng)忘卻了一小半≈较铮”
“啊镇草,已經(jīng)忘了一大半×鲋迹”
“不壞不壞梯啤,忘得真快,那么現(xiàn)在呢存哲?”
“已經(jīng)全都忘了因宇,忘得干干凈凈∷钔担”
“好了察滑,你上吧⌒蕹Γ”