preface

Recently, WHILE doing some refactoring work, I found a very troublesome but common problem in the project — “huge and bloated” View Controller! The reason for the trouble is that the View Controller contains too many business processes to start with. The common reason is that this status quo has become a common problem for many large projects! Thousands of lines at a time, very inconvenient to read, and later iteration and maintenance.

The analysis reason

After roughly reading some relatively complex View Controllers, I found the following reasons:

  • View Controller contains not only view binding/update, but also network requests, data parsing, business iteration logic, etc…
  • Business logic division is not clear, basically put together, just simple use#paramsTo differentiate
  • View controllers are referred to each other with poor mobility……

How make

In view of the above analysis, the next step is to think about how to solve it. In fact, we simply need to slim down the View Controller and cut out the unnecessary stuff. The point is how to split it up? Currently, the most popular architectures in the market are MVC, MVP and MVVM. The original project used MVC architecture. In fact, MVC is not bad, but if the hierarchy is not clear, it will lead to many problems. So this refactoring eliminates that option. If the MVVM architecture is used, dynamic binding techniques need to be utilized. Generally, ReactiveCocoa is selected as the dynamic binding scheme, which requires a certain learning cost and is troublesome to locate problems. Therefore, the MVP architecture was decided to be used for the refactoring.

MVP architecture

The view Controller contains almost all responsibility handling, as shown above in the architecture diagram before refactoring.

As shown in the figure above, a presenter layer and a Service layer are added compared to the original View Controller. Put the business logic in the Presenter layer, the data request/resolution in the Service layer, and the Model layer as entity model data only.

So what are the advantages of doing this?

  • Businesses can be well separated, for example if businesses P1,P2… Pn, so we can assign different business to different presenters. Of course, we do not mean that a business corresponds to a presenter. We can put the same/similar business in the same presenter. The granularity of the specific business depends on the situation. It’s just a little bit more flexible than just putting it in view Controller.

How to do that

As a simple example, suppose our View Controller contains two businesses, one is news list refresh and the other is feedback upload (forgive my imagination….). , as shown in the figure below:

The definition of the view

Many view Controllers in large projects use common controls such as view jump, load, and hints. To do this, we can define a public View Delegate and then a BaseViewController to implement it. This will make it easy to call these interfaces later.

@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
Copy the code

The definition of the presenter

We know that presenter needs to “act” on a view when working with business logic (note: the action is not really to update the view, but to tell the view to update it). So presenter needs to bind the View delegate.

@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
Copy the code
  • Q1: Why is the generic T used here

We use the generic T inherited from JBaseViewDelegate as the presenter’s view. The purpose of this is to ensure that the presenter can use the interface methods defined in JBaseViewDelegate and can extend them to meet business requirements. See the NewsViewDelegate at the back.

  • Q2: Why use member variables instead of member attributes

This is simple because member properties cannot be represented by the generic T, only by id, and doing so would result in the unrestricted interface methods of the subclass View Delegate.

News list serviceNewsPresenter

Here is a simple list of two functions, one is just enter the interface, refresh the data, the other is the drop-down refresh interface

@protocol NewsViewDelegate <JBaseViewDelegate>
@required
- (void)onRefreshData:(NSArray<NewsModel *> *)data;
@end

@interface NewsPresenter : JBasePresenter<id<NewsViewDelegate>>
- (void)loadData;
- (void)refreshData;
@end
Copy the code
  • Q: WhyNewsPresenterI’m going to implementid<NewsViewDelegate>

Let’s go back to the BasePresenter definition:

>, so the id

is actually the generic T here.

@interface NewsPresenter(a)
@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
Copy the code

As shown above, the relevant business logic and network requests are executed here. Of course, the network requests are not “real” network requests. The specific network requests are handled by the Service layer. After the result is returned, you can use the View Delegate interface to tell the view to do the update.

Feedback to the businessFeedbackPresenter

Here, the feedback service is simply simulated. Users input content, submit it, and finally respond to the result.

@interface FeedbackPresenter : JBasePresenter
- (void)submitFeedback:(NSString *)text;
@end

@interface FeedbackPresenter(a)
@property (nonatomic.strong) FeedbackService *service;
@end
@implementation FeedbackPresenter
- (void)submitFeedback:(NSString *)text {
    if(! text || text.length ==0) {[self.view showToast:@" Input is empty!!"];
        return;
    }
    [self.view showLoading];
    [self.service postFeedback:text completion:^(BOOL succeed) {
        [self.view hideLoading];
        if (succeed) {
            [self.view showToast:@" Upload successful"];
        } else{[self.view showToast:@" Upload failed"]; }}]; } - (FeedbackService *)service {if(! _service) { _service = [[FeedbackService alloc] init]; }return _service;
}
@end
Copy the code
  • Q: Why hereFeedbackPresenterYou don’t need to implement the protocol later?

Since there is no need to extend the JBaseViewDelegate in the feedback business, there is no need to implement an extended ViewDelegate. The JBaseViewDelegate view is bound by default.

Controller

We give the business logic to presenter and the network request to Service, so the controller has less responsibility. You only need to initialize the view and implement the View Delegate’s interface, and delegate the view’s interaction events to the 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
Copy the code

summary

MVP architecture can partition the business better than MVC architecture. For example, in the two business examples above, MVC would result in both businesses being placed in the View Controller. As the business increases, the Controller becomes very “bloated.” Of course, there are some disadvantages to the MVP, such as the increased number of interfaces and classes, but I think it’s worth it compared to the increasingly difficult controller to maintain. In addition, one of the most frustrating aspects of using MVP is how to classify presenters. Too little granularity leads to too many presenters, and too much granularity leads to too many “bloat” presenters. So how to grasp the granularity of the division is also to take time to think.

Finally, the Demo address is attached