Review: MVC/MVP/MVVM

Preface

This article was written by a friend on the MVC/MVP/MVVM framework to answer questions, aims to introduce the design idea of iOS under the MVC/MVP/MVVM three architectures and their advantages and disadvantages. The full text is about five thousand words, is expected to spend time reading 20 – 30 minutes.

MVC

  • The concept of
    MVC MVC is the earliest in desktop applications, M refers to the business data, V refers to the user interface, C is the controller. In the specific business scenarios, C is between M and V connection, is responsible for obtaining business data input, then the processed data to the output do the corresponding interface display, in addition, in the data has been updated, C also need to submit timely updates to the corresponding interface display. In the process, because M and V are completely isolated, so in the business scene, usually only need to replace the corresponding C complex with existing M and V can to quickly build a new business scene. MVC because of its reusability, greatly improving the efficiency of development, has been widely used in the end of development.

After the concept is over, take a look at the specific business scenarios in the MVC/MVP/MVVM are how to behave

  • MVC disappearance of the C layer
Review: MVC/MVP/MVVM
screen snapshot 2017-03-04 PM 3.16.48.png

on the page (business scenario) or similar page I believe we have done a lot, the specific implementation of each programmer may not be the same, here to talk about what I see part of the programmer’s writing:

//UserVC - (void) viewDidLoad [[UserApi new] fetchUserInfoWithUserId:132 {[super viewDidLoad]; completionHandler:^ (NSError *error, ID result) {if (error) {[self showToastWithText:@ "failed to obtain user information ~"];} else {self.userIconIV.image} =... = self.userSummaryLabel.text... [[userApi new]...}]; fetchUserBlogsWithUserId:132 completionHandler:^ (NSError *error, ID result) {if (error [self showErrorInView:self.tableView) {info:} {[...]; else self.blogs addObjectsFromArray:result]; [self.tableView reloadData];}}];} / /... (omitted - UITableViewCell * (UITableView * TA) tableView:) BleView cellForRowAtIndexPath: (NSIndexPath * indexPath) {BlogCell *cell = [tableView dequeueReusableCellWithIdentifier:@ = "BlogCell"]; cell.blog self.blogs[indexPath.row]; return cell;} - (void) tableView: (UITableView * tableView) didSelectRowAtIndexPath: (NSIndexPath *) indexPath pushViewController:[BlogDetailViewController instanceWithBlog:self.blogs[indexPath.row]] {[self.navigationController animated:YES];} / / slightly...
//BlogCell - (void) setBlog: (Blog) blog {_blog = blog; self.authorLabel.text = blog.blogAuthor; self.likeLebel.text = [NSString stringWithFormat:@ "%ld", blog.blogLikeCount];}

Programmers quickly finished code, Command+R run, there is no problem, do other things with go. Then one day, the product requirements of the business needs change, the user is shown in the page in other information, see your information, a draft of the show, like this:

Review: MVC/MVP/MVVM
screen snapshot 2017-03-04 PM 3.46.40.png

so small white code will be changed to this:

//UserVC - (void) viewDidLoad [super if ({viewDidLoad]; self.userId! = LoginUserId) {self.switchButton.hidden = self.draftTableView.hidden = YES; self.blogTableView.frame = [[UserApi}... New] fetchUserI... New] fetchUserBlogsWithUserId:132 completionHandler:^... [[UserApi (NSError *error, ID result) {//if Error... [self.blogs addObjectsFromArray:result] [self.blogTableView reloadData]; omitted;}]; [[userApi new] fetchUserDraftsWithUserId:132 completionHandler:^ (NSError *error, ID result) {//if Error... [self.drafts addObjectsFromArray:result] [self.draftTableView reloadData]; omitted;}];} - (NSInteger) tableView: (UITableView *) tableView numb ErOfRowsInSection: (NSInteger section) {return tableView = = self.blogTableView? Self.blogs.count: self.drafts.count;} / /... (omitted - UITableViewCell * (UITableView * tableView) tableView:) cellForRowAtIndexPath: (NSIndexPath * indexPath) {if (tableView = = self.blogTableView) {BlogCell *cell = [tableView dequeueReusableCellWithIdentifier:@ = "BlogCell"]; cell.blog self.blogs[indexPath.row]; return cell;} else {DraftCell *cell = [tableView dequeueReusableCellWithIdentifier:@ = "DraftCell"]; cell.draft self.drafts[indexPath.row]; return cell;}} - (void) tableView: (UITableView * tableView) didSelectRowAtIndexPath: (NSIndexPath * indexPath) {if (tableView = self.blogTableView) Slightly...} / /...
//DraftCell - (void) setDraft: (Draft) draft {_draft = draft; self.draftEditDate =} //BlogCell - (void) setBlog: (Blog) blog {}}

Then, the product that users see their pages plus a recycling station what will be very good, so programmers can add code logic, then…
with the change of requirement, UserVC becomes more and more bloated, more and more difficult to maintain, develop and test is also very poor. Programmers also found that code write some problems, but the specific problem where? Isn’t it MVC?
we will repeat the process with a diagram:

Review: MVC/MVP/MVVM
screen snapshot 2017-03-04 PM 4.35.35.png

through this map can be found, the user information page as a business scenario Scene needs to display a variety of data M (Blog/Draft/UserInfo), so the corresponding multiple View (blogTableView/draftTableView/image…), but each MV does not have a connection between the C layer should be distributed to each C layer processing logic are packaged into Scene this place, which is M-C-V to MM… -Scene-… VV, C layer so rather baffling disappeared.
also, as V two cell M (blog/ direct coupled draft), which means that the input was the two V at the end of the corresponding M. Multiplex
impossible. Finally, according to the business scene test abnormal trouble, because the business is bound to the initialization and destruction of the life cycle of VC, while the corresponding logic is also related to the And View click event, the test can only Command+R, point…

  • Correct MVC use posture

Perhaps the UIViewController class has brought confusion to the new, people mistakenly think that VC must be C layer in MVC, or Button, Label or View is too simple without the need of a C layer with, in short, seen too much since I work experience of projects such as “MVC” so, what is the correct use of MVC position?
is still cited above business scenarios, the correct MVC should look like this:

Review: MVC/MVP/MVVM
screen snapshot 2017-03-04 PM 6.42.04.png

UserVC as the business scene, to show the three kinds of data, corresponding to the three MVC, three MVC for each module of data acquisition, data processing and data display, and UserVC needs to do is to configure the three MVC layer, and data access from the notification of the C at the right time C, each layer to get the data of the corresponding treatment, after the treatment rendered to the respective View, each View UserVC will eventually have a good render the layout can be specific to the code as follows:

@interface BlogTableViewHelper: NSObject< UITableViewDelegate, UITableViewDataSource> (instancetype) + helperWithTableView: (UITableView *) tableView userId: (NSUInteger) - (void) userId; fetchDataWithCompletionHandler: (completionHander; NetworkTaskCompletionHander) - (void) setVCGenerator: (ViewControllerGenerator) VCGenerator; @end
@interface (BlogTableViewHelper) @property (weak, nonatomic) UITableView *tableView @property (copy, nonatomic); ViewControllerGenerator VCGenerator; @property (assign, nonatomic) NSUInteger userId @property (strong, nonatomic); NSMutableArray *blogs; @property (strong, nonatomic) UserAPIManager *apiManager; @end #define BlogCellReuseIdentifier @ "BlogCell" @implementation + BlogTableViewHelper (instancetype) (helperWithTableView: UITableView *) tableView userId: (NSUInteger) userId [[BlogTableViewHelper alloc] initWithTableView:tableView {return userId:userId];} - (instancetype) initWithTableView: (UITableView *) tableView userId: (NSUInteger userId) {if (self = [super init]) {self.userId = userId; tableView.delegat E = self; tableView.dataSource = self; self.apiManager = [UserAPIManager new]; self.tableView = tableView; __weak typeof (self) weakSelf = self; [tableView registerClass:[BlogCell class] forCellReuseIdentifier:BlogCellReuseIdentifier]; tableView.header = [MJRefreshAnimationHeader headerWithRefreshingBlock:^{// refreshUserBlogsWithUserId:userId completionHandler:^ (NSError pull-down refresh [weakSelf.apiManage *error, ID result) {/ /... Slightly}];}]; tableView.footer = [MJRefreshAnimationFooter headerWithRefreshingBlock:^{// load [weakSelf.apiManage loadMoreUserBlogsWithUserId:userId completionHandler:^ (NSError *error, ID result) {/ /... Slightly}]}]; return self;}}; #pragma mark - UITableViewDataSource & & Delegate; / /... - (NSInteger) tableView: slightly (UITableView *) tableView numberOfRowsInSection: (NSInteger) section {return self.blogs.count;} - (UITableViewCell *) tableView: (* UITableView) tableView (NSIndexPath * cellForRowAtIndexPath:) indexPath {BlogCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogCellReuseIdentifier]; BlogCellHelper *cellHelper = self.blogs[indexPath.row]; if (cell.didLikeHandler!) __weak typeof (cell) {weakCell = cell; [cell setDidLikeHandler:^{cellHelper.likeCount = 1; weakCell.likeCountText = cellHelper.likeCoun TText;}]}; cell.authorText = cellHelper.authorText; / /... All set return cell;} - (void) tableView: (UITableView * tableView) didSelectRowAtIndexPath: (NSIndexPath *) indexPath [self.navigationController pushViewController: self.VCGenerator (self.blogs[indexPath.row]) {animated:YES]}; #pragma mark - Utils - (void) fetchDataWithCompletionHandler: (NetworkTaskCompletionHander) completionHander new] refreshUserBlogsWithUserId:self.userId completionHandler:^ {[[UserAPIManager (NSError *error ID, result) {if (error) {[self} else {showErrorInView:self.tableView info:error.domain]; for (Blog *blog in result) {[self.blogs addObject:[BlogCellHelper help ErWithBlog:blog]] [self.tableView reloadData];}}; completionHandler? CompletionHandler (error, result): Nil;}];} / /... @end
@implementation BlogCell / /... - (void) onClickLikeButton: slightly (UIButton *) sender new] likeBlogWithBlogId:self.blogId userId:self.userId completionHandler:^ {[[UserAPIManager (NSError *error, ID result if (error)) {{//do error} else {/ / do success self.didLikeHandler (NIL)? Self.didLikeHandler: @end;}]}};
@implementation - BlogCellHelper (NSString * likeCountText) {return [NSString stringWithFormat:@ like%ld, self.blog.likeCount];} / /... (NSString * authorText) omitted - {return [NSString stringWithFormat:@ "Author: self.blog.authorName] @end;}% @".

The Blog module is composed of BlogTableViewHelper (C), BlogTableView (V), Blogs (C), here is a bit special, blogs inside is not M, but Cell C CellHelper, this is because the Blog MVC actually is composed of several smaller MVC. M and V are not what to say. Talk about the main C as what TableVIewHelper did.

In the actual development, each module of the View may be in the Scene in the corresponding Storyboard and new layout, this would not have to establish their own View modules (such as BlogTableViewHelper, let Scene here) to the C layer management on the line, of course, if you are a pure code, it requires the corresponding View the module was established. (for example, the following UserInfoViewController) to see their wishes, harmless.

BlogTableViewHelper provides a constructing method of data acquisition interface and the necessary, within the corresponding initialization according to their own situation.
when the external call interface fetchData, Helper will start the data access logic, because data acquisition may be related to some front page display (such as HUD), and specific display and is directly related to Scene (some Scene show HUD may show is a kind of style or no show), so this part will be in the form of CompletionHandler by Scene treatment.
in Helper, the data acquisition failure will show the corresponding error page is established successfully and notify the smaller part MVC display data (i.e., notify the CellHelper driver Cell), in addition, TableView pull to refresh and down loading logic is also attached to the Blog The module, so processing in Helper.
in the page logic, click the page by Scene directly through the VCGeneratorBlock configuration, so is decoupling (you can also use a didSelectRowHandler like mode to transfer data to the Scene layer, Scene made the jump, is the same. Finally,
, V) (Cell) now only exposed Set method for external settings, and so M (Blog) is isolated, reuse is no problem.

This series of processes are self management, in the future if the Blog module will be displayed in another SceneX, then SceneX only need to create a new BlogTableViewHelper, and then call helper.fetchData

DraftTableViewHelper and BlogTableViewHelper logic is similar, do not paste, simply paste the logic of the UserInfo module:

@implementation + UserInfoViewController (instancetype) instanceUserId: (NSUInteger) userId [[UserInfoViewController alloc] {return initWithUserId:userId];} - (instancetype) initWithUserId: (NSUInteger) userId [self addUI]; / / {...} / /... #pragma mark slightly - Action - (void) onClickIconButton: (UIButton *) sender [self.navigationController pushViewController:self.VCGenerator (self.user) {animated:YES]}; #pragma - mark Utils - (void) addUI UI {/ / self.userIconIV = [[UIImageView alloc] to initialize the layout of initWithFrame:CGRectZero]; self.friendCountLabel =......} - (void) fetchData new] fetchUserInfoWithUserId:self.userId completionHandler:^ {[[UserAPIManager (NSError *error, ID result) {if (error) {[self showErrorInView:self.view info:error.domain]} else {self.user = [User; objectWithKeyValues:result] = self.userIconIV.image; [UIImage imageWithURL:[NSURL URLWithString:self.user.url]]; / / self.friendCountLabel.text = [NSString stringWithFormat:@ data format like%ld, self.user.friendCount]; / / data format...}]};} @end

UserInfoViewController in addition to more than two TableViewHelper multiple addUI sub control layout method, the other is their logic is similar, the management of MVC, only need to initialize can be used in any Scene.

Now three self management module has been completed, UserVC needs only to do the corresponding assembly layout according to their own situation, and the building blocks:

@interface (UserViewController) @property (assign, nonatomic) NSUInteger userId @property (strong, nonatomic); UserInfoViewController *userInfoVC; @property (strong, nonatomic) UITableView *blogTableView @property (strong, nonatomic); BlogTableViewHelper *blogTableViewHelper; @end @interface SelfViewController UserViewController @property (strong, nonatomic) UITableView *draftTableView; @property (strong, nonatomic) DraftTableViewHelper * draftTableViewHelper; @end #pragma mark - UserViewController @implementation UserViewController (instancetype) + instanceWithUserId: (NSUInteger userId) {if (userId = = LoginUserId) {return} else {[[SelfViewController alloc] initWithUserId:userId]; return [[UserViewContr Oller alloc] initWithUserId:userId];}} - {(void) viewDidLoad [super viewDidLoad]; [self addUI]; [self configuration]; [self fetchData];} #pragma mark - Utils (UserViewController) - (void) addUI {/ / here is just to express specific meaning logic layout certainly isn't as simple as self.userInfoVC [UserInfoViewController = instanceWithUserId:self.userId]; self.userInfoVC.view.frame = CGRectZero; [self.view addSubview:self.userInfoVC.view]; [self.view addSubview:self.blogTableView = [[UITableView alloc] initWithFrame:CGRectZero style:0]] (void);} - configuration {self.title = @ "user details"; / /... Other settings (ID params) [self.userInfoVC setVCGenerator:^UIViewController * { Return [UserDetailViewController instanceWithUser:params];}]; self.blogTableViewHelper = [BlogTableViewHelper helperWithTableView:self.blogTableView userId:self.userId]; [self.blogTableViewHelper setVCGenerator:^UIViewController * (ID params) {return [BlogDetailViewController instanceWithBlog:params];}];} - (void) fetchData {[self.userInfoVC fetchData]; //userInfo module does not require any page loading [HUD show]; //blog HUD [self.blogTableViewHelper fetchDataWithcompletionHandler:^ module may need (NSError *error, ID result) {[HUD hide]}]; @end #pragma mark;} - SelfViewController @implementation - SelfViewController (void viewDidLoad) {[super viewDidLoad]; [self addUI]; [self configuration]; [self fetchData];} #pragma mark - Utils (SelfViewController) - (void) addUI addUI] [self.view {[super; addSubview:switchButton]; / / special part of... / /... AddSubview:self.draftTableView = UITableView + [self.view settings alloc] initWithFrame:CGRectZero style:0]];} - {[super (void) configuration configuration]; self.draftTableViewHelper = [DraftTableViewHelper helperWithTableView:self.draftTableView [self.draftTableViewHelper setVCGenerator:^UIViewController (ID * userId:self.userId]; params) {return [DraftDetailViewController instanceWithDraft:params];}];} - {[super (void) fetchData fetchData]; [self.draftTableViewHelper fetchData] @end;}

As the business scene of Scene (UserVC) to do something very simple, according to the configuration of its three modules (configuration), (addUI), and then notify the layout of each module to start (fetchData) can, because each module of the display and interaction is self managed, so Scene only can be responsible for and the business related. In addition, according to the access situation we create a subclass of UserVC SelfVC, SelfVC is doing something similar.

MVC said this is similar to the above comparison error of MVC, we see what problems to solve:
1 code reuse: three small module V (cell/userInfoView) Set method of foreign exposure only, M and even C are isolated, no reuse problem. Three modules MVC can also be used for the rapid construction of similar business scenarios (reuse modules will be worse, than the small module below I will explain).
2 code bloat: because Scene most of the logic and layout are transferred to the corresponding MVC, we only have assembled MVC constructed two different business scenarios business, each scene can normal corresponding data show, there is also logic interaction accordingly, and these things, with space is about 100 lines of code (of course, here I ignored your Scene layout generation Code).
3 Excelstor Exhibition: both products in the future want to add the recycle bin or towers, I just need the new MVC module corresponding to the corresponding Scene,
. 4 maintenance: separation of duties between each module, where the error change is completely does not affect other modules. In addition, each module the code is not much, even if one day write code people leave the guy according to error can quickly locate the error module.
5 testability: Unfortunately, the business is still bound initialization in the life cycle of Scene, and some logic still need to UI click event, we still only Command+R little…

  • Disadvantages of MVC

As you can see, even the standard MVC framework is not perfect, there are still some problems difficult to solve, so the disadvantages of MVC are summarized as follows:
where? 1 excessive focus on isolation: the fact that MV (x) series has the shortcomings, in order to achieve the complete isolation of the V layer, V external exposure to Set method. Generally not what problem, but when many attributes need to be set, a large number of repeated Set method to write up or tiring.
2 business logic and business show strong coupling: as you can see, some of the business logic (page Jump / praise / share…) is directly scattered in the V layer, the that means we were testing the logic, we must first generate the corresponding V, and then tested. Obviously, this is not reasonable. Because the business logic is a change in the final data M, our focus should be on the M, and Not show M V.

  • MVP

The disadvantage is that MVC did not distinguish between business logic and business show, this is not friendly to the unit test. MVP was optimized for the above shortcomings, it will show the business logic and business also made a layer of isolation, the corresponding M becomes MVCP. and V function unchanged, the original C now is only responsible for the layout, and all the logic of all transferred to the P layer.

The corresponding relationship is shown in Figure:

Review: MVC/MVP/MVVM
screen snapshot 2017-03-05 PM 2.57.53.png

The business scene did not change, still is to show three types of data, only three MVC replaced three MVP (I’m only drew Blog module), UserVC is responsible for the allocation of three MVP (each new VP, created by VP C, C will be responsible for the binding relationship between VP), and notify the P each layer in the appropriate time (before the notice of C layer) for data acquisition, each layer in the P access to the data after the corresponding treatment, after the treatment, View data binding advice has been updated, V received the update notification from P formatted data access page rendering, each View UserVC finally have a good render the layout. In addition, the V layer C layer is no longer any business logic, all events trigger the corresponding command all calls to the P layer, specific to the code as follows:

@interface BlogPresenter: NSObject + (instancetype) instanceWithUserId: (NSUInteger) - userId (NSArray *); allDatas; / / moved to the P business logic layer and business related to M with P layer (void) - refreshUserBlogsWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler; (void) - loadMoreUserBlogsWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler; @end
@interface (BlogPresenter) @property (assign, nonatomic) NSUInteger userId @property (strong, nonatomic); NSMutableArray *blogs; @property (strong, nonatomic) UserAPIManager *apiManager; @end @implementation BlogPresenter (instancetype) + instanceWithUserId: (NSUInteger) userId [[BlogPresenter alloc] {return initWithUserId:userId];} - (instancetype) initWithUserId: (NSUInteger) {if (self = userId [super init]) {self.userId = userId; self.apiManager = UserAPIManager / new]; / / #pragma}}... Slightly - Interface - mark (NSArray * allDatas) {return self.blogs;} / / for outer commands - (void) refreshUserBlogsWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler {[self.apiMa Nager refreshUserBlogsWithUserId:self.userId completionHandler:^ (NSError *error, ID result) {if (! Error) {[self.blogs removeAllObjects]; / / empty data before for (Blog *blog in result [self.blogs addObject:[BlogCellPresenter) {presenterWithBlog:blog]]}}; completionHandler? CompletionHandler (error, result): Nil;}];} / / to the outer call command (void) loadMoreUserBlogsWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler self.apiManager loadMoreUserBlogsWithUserId:self.userId completionHandler {[@end]}...
@interface BlogCellPresenter: NSObject + (instancetype) presenterWithBlog: (Blog * blog); - (NSString * authorText); - (NSString * likeCountText); - (void) likeBlogWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler; (void) - shareBlogWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler; @end
@implementation - BlogCellPresenter (NSString * likeCountText) {return [NSString stringWithFormat:@ like%ld, self.blog.likeCount];} - {return (NSString *) authorText [NSString stringWithFormat:@ "author name:% @", self.blog.authorName];} / /... Slightly - (void) likeBlogWithCompletionHandler: (NetworkTaskCompletionHander) completionHandler new] likeBlogWithBlogId:self.blogId userId:self.userId completionHandler:^ {[[UserAPIManager (NSError *error, ID result if (error)) {fail} else {//do {//do success self.blog.likeCount + = 1;} completionHandler? CompletionHandler (error, result): Nil;}];} / /... @end

BlogPresenter and BlogCellPresenter were used as the P layer of BlogViewController and BlogCell, is a series of business logic set. BlogPresenter is responsible for obtaining Blogs raw data and through the original data structure of BlogCellPresenter, and BlogCellPresenter provides a variety of data formats of good for Cell rendering, in addition, point praise and sharing business now are transferred to here.

The business logic is transferred to the P layer, V layer is the only need to do two things:
1 monitor P layer data update notification, refresh the page display.
2 in the click event is triggered, the corresponding method calls to the P layer, and the method of implementation results show.

@interface BlogCell: UITableViewCell @property (strong, nonatomic) BlogCellPresenter *presenter; @end
@implementation BlogCell - (void) setPresenter: (BlogCellPresenter * presenter) {_presenter = presenter; / / get the formatted data from the Presenter show self.authorLabel.text = presenter.authorText; self.likeCountLebel.text = presenter.likeCountText;} / /... #pragma mark slightly - Action - (void) onClickLikeButton: (UIButton * sender) {[self.presenter likeBlogWithCompletionHandler:^ (NSError *error, ID result) {if (error!) {// page refresh self.likeCountLebel.text = self.presenter.likeCountText;} / /... Slightly}]}; @end

Between the C layer to do is layout and the binding of PV (here may not be obvious, because the layout code inside the BlogVC is TableViewDataSource, PV bound, because I am lazy with the Block notification callback, so also not too obvious, if the Protocol callback is obvious), the code is as follows:

@interface BlogViewController: NSObject + (instancetype) instanceWithTableView: (UITableView *) tableView presenter: (BlogPresenter) presenter; (void) - setDidSelectRowHandler: (void (^) (Blog * didSelectRowHandler)); - (void) fetchDataWithCompletionHandler: (NetworkCompletionHandler) completionHandler; @end
@interface BlogViewController (<); UITableViewDataSource, UITabBarDelegate, BlogView> @property (weak, nonatomic) UITableView *tableView @property (strong, nonatomic); BlogPresenter presenter; @property (copy, nonatomic) void (^didSelectRowHandler) (Blog); @end @implementation (instancetype) BlogViewController + instanceWithTableView: (UITableView *) tableView presenter: (BlogPresenter) {presenter return [[BlogViewController alloc] initWithTableView: tableView presenter:presenter];} - (instancetype) initWithTableView: (UITableView *) tableView presenter: (BlogPresenter presenter) {if (self = [super init]) {self.presenter = presenter; self.tableView = tableView; tableView.delegate = self; tableView .dataSource = self; __weak typeof (self) weakSelf = self; [tableView registerClass:[BlogCell class] forCellReuseIdentifier:BlogCellReuseIdentifier]; tableView.header = [MJRefreshAnimationHeader headerWithRefreshingBlock:^{// weakSelf.presenter refreshUserBlogsWithCompletionHandler:^ (NSError pull-down refresh [*error, ID, result) {[weakSelf.tableView.header endRefresh]; if (! Error) {[weakSelf.tableView reloadData];} / /...}] omitted;}]; tableView.footer = [MJRefreshAnimationFooter headerWithRefreshingBlock:^{// load [weakSelf.presenter loadMoreUserBlogsWithCompletionHandler:^ (NSError *error, ID result) {[weakSelf.tableView.footer endRefresh]; if (! Error) {[weakSelf.tableView reloadData];} / /... Slightly}]}]; return self;}}; #pragma mark - Interface - (void) fetchDataWithCompletionHandler: (NetworkCompletionHandler) completionHandler refreshUserBlogsWithCompletionHandler:^ (NSError {[self.presenter *error, ID result) {if (error) {//show error info} else {[self.tableView reloadData];} completionHandler? CompletionHandler (error, result): Nil}]; #pragma mark;} - UITableViewDataSource & & Delegate (NSInteger) - tableView: (UITableView *) tableView numberOfRowsInSection: (NSInteger) section {return self.presenter.allDatas.count;} - (UITableViewCell *) tableView: (* UITableView) tableView cellForRowAtIndexPath: (NSIndexPath * indexPath) {BlogCell *cell = [tableView dequeueReusableCellWithIdentifier:BlogCellReuseIdentifier]; BlogCellPresenter *cellPresenter = self.presenter.allDatas[indexPath.row]; cell.present = cellPresenter; return cell;} - (void) tableView: (UITableView * tableView * NSIndexPath (didSelectRowAtIndexPath:) indexPath) {self.didSelectRowHandler? Self.didSelectRowHandler (self.presenter.allDatas[indexPath.row]): Nil; @end}

BlogViewController is no longer responsible for the actual data acquisition logic, interface, data acquisition directly call Presenter in addition, because the business logic is also transferred to the Presenter, so the layout of TableView is also used for Presenter.allDatas. Cell display, we replaced the original large Set method, and make the Cell according to the binding of CellPresenter to do the show. After all, now the logic is moved to the P layer, P layer and V Layer command to make corresponding interaction must rely on the corresponding, but V and M are still isolated, just and P coupling, P layer can replace the M, obviously not, this is a compromise.

Finally, Scene, it’s not much change, just replace the configuration MVC for configuration MVP, in addition to the data acquisition is to take the P layer, do not go C layer (however, the code is not the case):

- (void) configuration BlogPresenter *blogPresenter {/ /... Other settings = [BlogPresenter self.blogViewController = [BlogViewController instanceWithUserId:self.userId]; instanceWithTableView:self.blogTableView presenter:blogPresenter]; setDidSelectRowHandler:^ [self.blogViewController (Blog *blog) {[self.navigationController pushViewController:[BlogDetailViewController instanceWithBlog:blog] animated:YES];}]; / /...} - slightly (void) fetchData {/ /... FetchData] [HUD show] [self.userInfoVC; [self.blogViewController; fetchDataWithCompletionHandler:^ (NSError *error, ID result) {[HUD hide]; / /}]; or because of lazy, with Block go C layer forwarding will write code, or if it is Protocol Who KVO will use a self.blogViewController.presenter / / but Never mind, because we replace the MVC MVP in order to solve the problem of unit testing, now the usage does not affect the unit test, and only the concept does not match it. Slightly} / /...

The above example is actually a problem, we assume that all events are initiated by the V layer and one-time. This fact is not established, cite a simple example: similar to WeChat voice chat on the page, click on the Cell Cell voice began to play, show broadcast animation, broadcast animation complete stop, and then play a
voice. In this play in the scene, if CellPresenter was like the above offer only a playWithCompletionHandler interface is not feasible because the play after the callback is definitely in the C layer, C layer will be found when the execution command CellPresenter cannot tell Cell theanimations stop after that is not a one-time event trigger. In addition, the play is complete, the C layer traversal to the next to be broadcast call CellPresenterX player interface, CellPre SenterX because it does not know who is the corresponding Cell, certainly will not be able to notify Cell to start animation, event sponsors are not necessarily V.
layer for these non disposable or other layer initiated events, processing method is very simple, just add a Block attribute in the CellPresenter, because it is the property, Block can be repeated callback, Block also can capture Cell, so don’t worry about can not find the corresponding Cell. or so:

@interface VoiceCellPresenter: NSObject @property (copy, nonatomic) void (^didUpdatePlayStateHandler) (NSUInteger); - (NSURL *) playURL; @end
@implementation VoiceCell - (void) setPresenter: (VoiceCellPresenter * presenter) {_presenter = presenter; if (presenter.didUpdatePlayStateHandler!) __weak typeof (self) {weakSelf = self; [presenter setDidUpdatePlayStateHandler:^ (NSUInteger playState) {switch (playState) {case Buffering: break; case Playing: weakSelf.playButton... WeakSelf.playButton... Break case Paused:; weakSelf.playButton break;}}]...}};

When playing, VC only need to keep a CellPresenter, then introduced the corresponding playState call didUpdatePlayStateHandler can update the status of the Cell
. Of course, if it is a VP binding Protocol way, so do these things is very common, do not write.

MVP will be like this, compared to MVC, it actually do only one thing, namely segmentation show business and the business logic separate display and logic, as long as we can guarantee the V update notification from P in the data can refresh the page, then the entire business is no problem. Because the notice received by V it is from the P layer data access / update operation, so long as we guarantee that the operation P layer is normal. We can only test the P layer logic, do not care about the V layer.

  • MVVM

In fact, MVP is already a very good framework, solves almost all known problems, so why MVVM?
is still for example, assume that there is now a Cell, click on Cell button can be attention and attention, can also be cancelled in concern, cancel concern, SceneA the first pop and SceneB ask, do pop, then cancel the attention operation and business setting strong association, so the interface may not be V layer called directly, will rise to the Scene layer. Specific to the code, probably like this:

@interface UserCellPresenter: NSObject @property (copy, nonatomic) void (^followStateHander) (BOOL isFollowing); @property (assign, nonatomic) BOOL isFollowing; (void) follow;
@implementation UserCellPresenter (void follow) {if (self.isFollowing!) {// not to pay attention to / / follow user else} {// has concerned to cancel self.followStateHander? Self.followStateHander (YES): Nil; / / the first notification Cell display follow state [[FollowAPIManager new] unfollowWithUserId:self.userId completionHandler:^ (NSError *error, ID result) {if {self.followStateHander (error) self.followStateHander? (NO): Nil; //follow eles {self.isFollowing} failed state rollback = YES;}}} / /...}] slightly; @end
@implementation UserCell - (void) setPresenter: (UserCellPresenter * presenter) {_presenter = presenter; if (_presenter.followStateHander!) __weak typeof (self) {weakSelf = self; [_presenter setFollowStateHander:^ (BOOL isFollowing) {[weakSelf.followStateButton: setImage:isFollowing?...];}];}} - (void) onClickFollowButton: (UIButton * button) {// will focus on the button click the upload [self routeEvent:@ "followEvent" userInfo:@{@ "presenter": self.presenter}] @end;}
Confirm whether the implementation of event @implementation FollowListViewController / intercept click event after the judgment - (void) routeEvent: (NSString * eventName) userInfo: (NSDictionary * userInfo) {if ([eventName isEqualToString:@ "followEvent"]) {UserCellPresenter *presenter userInfo[@ = "presenter"]; [self showAlertWithTitle:@ "prompt" message:@ "to confirm the cancellation of his attention?" cancelHandler:nil confirmHandler: [presenter follow] ^{;}]; @end}}
@implementation UIResponder (Router) / / the responder chain event upload event was eventually discarded without treatment or intercept along - (void) routeEvent: (NSString * eventName) userInfo: (NSDictionary *) userInfo [self.nextResponder routeEvent: eventName userInfo:userInfo] {@end};

Block looks a little cumbersome, we look at the Protocol:

@protocol UserCellPresenterCallBack < NSObject> - (void) userCellPresenterDidUpdateFollowState: (BOOL) isFollowing; @end @interface UserCellPresenter: NSObject @property (weak, nonatomic) id< UserCellPresenterCallBack> view; @property (assign, nonatomic) - (void) BOOL isFollowing; follow; @end
@implementation UserCellPresenter (void follow) {if (self.isFollowing!) {// not to pay attention to / / follow user else} {// has concerned to cancel concern BOOL isResponse = (userCellPresenterDidUpdateFollowState) [self.view respondsToSelector:@selector]; isResponse? [self.view userCellPresenterDidUpdateFollowState:YES] [[FollowAPIManager new] unfollowWithUserId:self.userId: Nil; completionHandler:^ (NSError *error, ID result) {if {isResponse (error) [self.view? UserCellPresenterDidUpdateFollowState:NO]: Nil;} eles {self.isFollowing = YES;}}} / /...}] slightly; @end
@implementation UserCell - (void) setPresenter: (UserCellPresenter * presenter) {_presenter = presenter; _presenter.view = self;} #pragma mark - UserCellPresenterCallBack - (void) userCellPresenterDidUpdateFollowState: (BOOL) isFollowing: setImage:isFollowing {[self.followStateButton}];?...

The removal of Alert Route and VC in such code, we can find that whether Block or Protocol because of the need to isolate the page display and business logic code, Shangrao in a circle, virtually added the amount of code a lot, this is just an event like this, if there is more than one? That writes really hurt…

Look at the above code will be found, if we continue to add events, so most of the code is to do one thing: the P layer data updates to the V layer. Block will add a lot of properties in the P layer, add a lot of Block logic in the V layer. And Protocol P although only add layer a property, but inside the Protocol method would have been increased, corresponding to the V layer will need to increase the
method. Since the problem is found, then try to solve it, to achieve low coupling communication between two objects to OC, except for Block and Protocol, generally thought we look at the KVO. KVO in the above example behave:

@interface UserCellViewModel: NSObject @property (assign, nonatomic) BOOL isFollowing; (void) follow; @end
@implementation UserCellViewModel (void follow) {if (self.isFollowing!) {// not to pay attention to / / follow user else} {// has concerned to cancel self.isFollowing = YES; / / Cell follow [[FollowAPIManager to notify the state of new] unfollowWithUserId: self.userId completionHandler:^ (NSError *error, ID result) {if (error) {self.isFollowing = NO;}//follow failure state back slightly}];}} / /... @end
@implementation UserCell (void awakeFromNib) {@weakify (self); [RACObserve (self, viewModel.isFollowing) subscribeNext:^ (NSNumber *isFollowing) {@strongify (self); [self.followStateButton setImage:[isFollowing boolValue]?:];};}...

The code is less about half, in addition, logical read more clearly, Cell was observed to bind ViewModel isFollowing state, and in the status change, update their display.
three data notice a simple comparison, believe that what kind of way more friendly to the programmer, everyone knows not here.
is now about the mention of MVVM will think of RAC, but they are not what, for MVVM RAC only provides a data binding way elegant safe, if you do not want to learn RAC, own a KVOHelper or something is also possible. In addition, the charm of RAC lies in function response type programming, we should not only apply it is limited to MVVM, use should also be used daily in the development of
. On MVVM, I want to say is this What more, because MVVM is only MVP binding evolution body, removing the data binding mode, and the other MVP is exactly the same, only possible presentation is Command/Signal instead of CompletionHandler and the like, so not to repeat them.

Finally, make a brief summary:
1.MVC as the old architecture, has the advantages of the business scene by the display data type is divided into several modules, each module in the C layer is responsible for business logic and business show, while M and V should be isolated from each other for reuse, other each module can also be handled properly reuse unit. The split is decoupled, the way to do is to isolate burden, reuse, improve development efficiency. The disadvantage is that there is no distinction between business logic and business friendly display, not for unit testing.
2.MVP as the advanced version of MVC, is proposed to distinguish between business logic and business presentation, business logic will be all transferred to the P layer, data receiving the P layer V layer updates page display. Has the advantages of good stratification brought a friendly unit test, shortcomings in the hierarchical logic advantage will make the code around, at the same time It also brings a lot of code, not friendly to the programmer. As
3.MVVM synthesizer, through binding data to update the data, reducing the amount of code, while optimizing the code logic, just learning a high cost, not friendly enough for beginners.
4.MVP and MVVM MVC will be established so because stratified two times the above documents, code management good.
5 in MVP and MVVM, between V and P or VM theory is many to many relationship, different layout in the same logic only need to replace the V layer, and the same layout of different logic only need to replace P or VM layer. But in the actual development of P or VM because of coupling of the V display logic layer to degenerate into a one-to-one relationship (such as the SceneA to display “xxx+Name”, VM Name will be formatted as “XXX + Name”. One day S CeneB also used this module, all the click event and page display are the same, just Name display “YYY + Name”, the VM for showing the logical coupling of SceneA, it is embarrassing), for such cases, there are usually two ways, one is to judge the output state in the VM layer and the state one is in the VM layer, adding a layer of FormatHelper. because the former may appear too many states code ugly, although the latter is elegant and expand, but too much of the data reduction when stratified in slightly clumsy, we should choose according to need.

Here is just a nonsense, some articles to say that MVVM is the C layer in order to solve the problem of testing over MVC, in fact is not the case. According to the architecture evolution sequence, the C layer is not a good resolution of most bloated MVC module, a good resolution on the line, do not need MVVM. and MVC to test can also be used MVP to solve, only MVP is not perfect, in the data exchange between the VP is too complicated, so it raises MVVM. when MVVM the body, we can see the origin from the results, it was doing a lot of things, but not that its predecessors and a lot of effort!

  • Architecture so much, in the end how to choose the daily development?

Whether it is MVC, MVP, MVVM or MVXXX, the final goal is to serve the people, we focus on the architecture, focusing on stratification is to develop efficiency, in order to ultimately happy. So, in the actual development should not rigidly adhere to a particular architecture, based on the actual project, the general MVC can meet most of the development as for the demand, MVP and MVVM, you can try, but do not force
. In short, I hope you can do: design, aware of. Line code, just happy.

This article with the demo address