最近在作一些重构项目的工做,发现项目中存在一个很是麻烦却又很常见的问题——“庞大臃肿”的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地址