最近在作一些重構項目的工做,發現項目中存在一個很是麻煩卻又很常見的問題——「龐大臃腫」的view controller!麻煩的緣由是view controller中包含太多業務處理了,難如下手。而說常見的緣由是由於這種現狀已經成爲不少大型項目的通病了!動不動就上千行,閱讀起來很是不便,後期迭代和維護也很麻煩。git
大體閱讀了一些較爲複雜的view controller,發現主要包括如下緣由:github
#params
來劃分針對上面的緣由分析,接下來就要思考怎麼解決。其實,很簡單,咱們就是要對view controller進行「瘦身」,把一些沒必要要的東西分割出去。重點是如何去分割呢?目前市場上比較流行的架構即是MVC、MVP和MVVM三種了。本來項目採用的即是MVC架構,其實並不是MVC很差,只是分層不清晰的話,就會致使不少問題。因此此次重構就摒除了這種選擇。若採用MVVM架構的話,則須要利用到動態綁定技術。通常會選取ReactiveCocoa做爲動態綁定方案,而它須要必定的學習成本,且定位問題起來也比較麻煩。因此,最終決定採用MVP架構來進行重構。網絡
未重構前的架構圖如上所示,view controller幾乎包含了全部職責處理。架構
如上圖所示,對比原來的view controller,新增了presenter層和service層。將業務邏輯放在presenter層,數據請求/解析放在service層,model層只是做爲實體模型數據。ide
那麼這麼作,有什麼優點呢?post
咱們以一個簡單的例子來講明,假設咱們的
view controller
中包含兩個業務,一個是新聞列表刷新,另外一個是反饋內容上傳(原諒我這腦洞....),具體以下圖所示:學習
在大型項目中不少
view controller
中須要用到不少公共的控件,好比視圖跳轉、加載和提示等。爲此,咱們能夠先定義一個公共的view delegate
,而後定義一個BaseViewController
去實現它。這樣咱們在後面就能夠很方便去調用這些接口。ui
@protocol JBaseViewDelegate <NSObject>
@required
- (void)pushViewController:(UIViewController *)controller;
- (void)popViewController;
- (void)showToast:(NSString *)text;
- (void)showLoading;
- (void)hideLoading;
@end
@interface JBaseViewController : UIViewController <JBaseViewDelegate>
@end
@implementation JBaseViewController
....
@end
複製代碼
咱們知道presenter在處理業務邏輯時,須要對視圖進行"操做"(注意:這裏的操做並不是真正去更新視圖,而是通知view去更新)。因此presenter須要對
view delegate
進行綁定。atom
@interface JBasePresenter<T : id<JBaseViewDelegate>> : NSObject {
__weak T _view;
}
- (instancetype)initWithView:(T)view;
- (T)view;
@end
@implementation JBasePresenter
- (instancetype)initWithView:(id<JBaseViewDelegate>)view {
if (self = [super init]) {
_view = view;
}
return self;
}
- (id<JBaseViewDelegate>)view {
return _view;
}
@end
複製代碼
咱們使用繼承
JBaseViewDelegate
的泛型T做爲presenter的視圖,這麼作的目的是能夠保證presenter可使用JBaseViewDelegate
中定義的接口方法,而且能夠對其進行擴展,以知足業務要求,具體能夠看後面的NewsViewDelegate
。spa
很簡單,由於成員屬性沒法使用泛型T表示,只能使用id來表示泛型,若是這麼作會致使子類
view delegate
的接口方法沒有限制。
NewsPresenter
這裏簡單地列舉了兩個功能,一個是剛進入界面時,刷新數據,另外一個是下拉刷新界面
@protocol NewsViewDelegate <JBaseViewDelegate>
@required
- (void)onRefreshData:(NSArray<NewsModel *> *)data;
@end
@interface NewsPresenter : JBasePresenter<id<NewsViewDelegate>>
- (void)loadData;
- (void)refreshData;
@end
複製代碼
NewsPresenter
後面要實現id<NewsViewDelegate>
咱們回到以前
BasePresenter
的定義:<T : id<JBaseViewDelegate>>
,因此這裏的id<NewsViewDelegate>
其實就是這裏的泛型T。
@interface NewsPresenter()
@property (nonatomic, strong) NewsService *service;
@end
@implementation NewsPresenter
- (void)loadData {
[self.view showLoading];
__weak typeof(self) weakSelf = self;
[self.service loadNewsDataWithCompletion:^(NSArray<NewsModel *> *data) {
[weakSelf.view hideLoading];
[weakSelf.view onRefreshData:data];
}];
}
- (void)refreshData {
[self.view showLoading];
__weak typeof(self) weakSelf = self;
[self.service refreshNewsDataWithCompletion:^(NSArray<NewsModel *> *data) {
[weakSelf.view hideLoading];
[weakSelf.view onRefreshData:data];
}];
}
#pragma mark - getter
- (NewsService *)service {
if (!_service) {
_service = [NewsService new];
}
return _service;
}
@end
複製代碼
如上所示,相關的業務邏輯和網絡請求都是在這裏執行,固然這裏的網絡請求並非「真正」的網絡請求,具體的網路請求是交給service
層去處理的。返回結果以後,就能夠經過view delegate
接口去通知view
去作相關更新工做。
FeedbackPresenter
這裏簡單模擬反饋業務,用戶輸入內容,提交以後,最後響應結果。
@interface FeedbackPresenter : JBasePresenter
- (void)submitFeedback:(NSString *)text;
@end
@interface FeedbackPresenter ()
@property (nonatomic, strong) FeedbackService *service;
@end
@implementation FeedbackPresenter
- (void)submitFeedback:(NSString *)text {
if (!text || text.length == 0) {
[self.view showToast:@"輸入的內容爲空!!!"];
return;
}
[self.view showLoading];
[self.service postFeedback:text completion:^(BOOL succeed) {
[self.view hideLoading];
if (succeed) {
[self.view showToast:@"上傳成功"];
} else {
[self.view showToast:@"上傳失敗"];
}
}];
}
- (FeedbackService *)service {
if (!_service) {
_service = [[FeedbackService alloc] init];
}
return _service;
}
@end
複製代碼
FeedbackPresenter
後面不須要實現協議呢?由於反饋業務中並不須要對
JBaseViewDelegate
進行擴展,因此這裏不須要額外實現擴展的view delegate
。默認會綁定JBaseViewDelegate
視圖。
咱們將業務邏輯交給presenter,網絡請求交給service,那麼這時controller的職責就變得不多了。只需負責視圖初始化和實現
view delegate
的接口,以及對視圖的交互事件委託給presenter處理。
@interface ViewController : JBaseViewController
@end
@interface ViewController () <UITableViewDelegate, UITableViewDataSource, NewsViewDelegate>
....
@property (nonatomic, strong) NewsPresenter *newsPresenter;
@property (nonatomic, strong) FeedbackPresenter *feedbackPresenter;
@end
....
#pragma mark - NewsViewDelegate
- (void)onRefreshData:(NSArray<NewsModel *> *)data {
if (!self.dataSource) {
self.dataSource = [NSMutableArray array];
}
[self.dataSource addObjectsFromArray:data];
[self.tableView reloadData];
}
#pragma mark - event
- (void)pullToRefresh {
[self.refresh endRefreshing];
[self.newsPresenter refreshData];
}
- (void)sendBtnDidClick:(UIButton *)btn {
[self.feedbackPresenter submitFeedback:self.textView.text];
}
#pragma mark - getter
- (NewsPresenter *)newsPresenter {
if (!_newsPresenter) {
_newsPresenter = [[NewsPresenter alloc] initWithView:self];
}
return _newsPresenter;
}
- (FeedbackPresenter *)feedbackPresenter {
if (!_feedbackPresenter) {
_feedbackPresenter = [[FeedbackPresenter alloc] initWithView:self];
}
return _feedbackPresenter;
}
@end
複製代碼
對比使用MVC架構,MVP架構能比較好地對業務進行劃分,好比上面的兩個業務例子,若使用MVC,那麼勢必會致使兩個業務都會放置在view controller中。而隨着業務的增長,controller就會變得很是「臃腫」了。固然MVP也存在一些劣勢,好比接口和類的數量會增長,但我的以爲對比起愈來愈難維護的controller來講,這也是值得的。此外,使用MVP比較讓人頭疼的一點,就是如何劃分presenter,粒度太小會致使presenter過多,粒度過大,又會致使presenter過於「臃腫」。因此如何把握好粒度的劃分也是要花時間去思考的。
最後附上Demo地址