preface
It’s been 11 months since the last blog was published. Sin, sin. This year time is also enough to toss about, at the beginning of the year quit job to the goose factory, alone responsible for a community project, busy day dark, busy almost, in a moment to July.
In July, the heart also followed the hot, the eye looked at the mobile terminal this development trend has been downhill since 05 years, thinking of going on so no, have to find a way back. Read on the net, feel front-end is good, recently fry of quite hot, that learn to read, bought HTML, CSS, JS of a few books, spent a month of leisure time to read, incidentally did a few demo, suddenly feel good boring. Probably iOS is also write interface, front-end or write interface, write some numb. I’ve been learning Python, writing some crawlers, and using Django as a backend. It’s been a lot of fun, and Python has done a great job in big data and AI, so I want to take this opportunity to learn more.
Although the entry threshold of these two fields is relatively high, it should be a development trend according to the current development momentum. The wave of Internet in the past decade was mobile Internet, and the wave in the next decade is likely to be AI. So prepare early and learn from scratch. In fact, as a programmer, anxiety is inevitable, because what little knowledge you have is a drop in the ocean compared with the rapid development of technology. It makes people sigh that learning is infinite while life is finite.
Many people get lost in the process of chasing new technology and become more and more anxious because they find that no matter how much they learn, they can’t keep up with the pace of technological development. In my opinion, it is better to lay solid basic skills, such as systems, data structures, algorithms and networks, in pursuit of new technologies that are not yet known to be able to be implemented. New technologies emerge one after another, and the chaos is becoming fascinating, but in the final analysis, they are built on these basic knowledge.
Let’s have a separate article about how to study sometime. Let’s get to the topic today. Let’s talk about the applications of several major architectures in the field of iOS development, including MVC, MVP, MVVM and VIPER. Those who do iOS development are generally familiar with MVC, because Apple has customized the MVC architecture suitable for iOS development for us.
But in the process of writing code, we must have these questions: why my VC is getting bigger and bigger, why feel Apple MVC strange not like a real MVC, network request logic on which layer, the Internet is hot MVVM is worth learning, VIPER is what ghost?
I hope the following text can remove these doubts for you, I will compare and analyze these frameworks in multiple dimensions, point out their advantages and disadvantages, and then combine a specific DEMO with different architectures to achieve, so that you have an intuitive understanding of these architectures. Of course, these are just for the purpose of introducing jade, the elaboration is also my personal understanding, if there are mistakes, welcome to point out, we discuss progress together ~~
MVC
1. The ideal model for MVC
The ideal model for MVC looks like this:
The responsibilities of each layer are as follows:
- Models: Data layer, the data interface layer responsible for processing and retrieving data.
- Views: Display layer (GUI). For iOS, all classes that start with UI are basically in this layer.
- Controller: The Controller layer, which is the glue or middle man between the Model and the View. In general, it is responsible for modifying the Model when the user has an action on the View; It is responsible for updating the View when the value of the Model changes.
As shown in the figure above, M and View should be completely isolated, with C acting as an intermediary responsible for the interaction between them. Meanwhile, the three are completely independent and separate, which can ensure the testability and reusability of M and V. However, generally, C acts as an intermediary for M and V in special application scenarios, so it is difficult to reuse.
2. Implementation of MVC in iOS
But actually the way MVC is implemented in iOS is very difficult to do that, because because of Apple’s specification, every interface presentation requires a viewController to be built, and each viewController has a root View, This results in C and V being tightly coupled to form the C layer in iOS, which clearly defeats the purpose of MVC.
The actual MVC in Apple looks something like this:
That’s where massive Controller came from, more on that in a second. So why did Apple do it? For the full version of Apple’s explanation of MVC, the following quote is from one of them. To put it simply, the viewController in iOS is actually a combination of view and controller to improve development efficiency and simplify operations.
Apple MVC specification
From the link above
One can merge the MVC roles played by an object, making an object, for example, Fulfill both the controller and view roles — in which case, it would be called a view controller. In the same way, you can also have model-controller objects. For some applications, combining roles like this is an acceptable design.
A model controller is A controller that concerns itself mostly with the model layer. It “owns” the model; its primary responsibilities are to manage the model and communicate with view objects. Action methods that apply to the model as a whole are typically implemented in a model controller. The document architecture provides a number of these methods for you; for example, an NSDocument object (which is a central part of the document architecture) automatically handles action methods related to saving files.
A view controller is a controller that concerns itself mostly with the view layer. It “owns” the interface (the views); its primary responsibilities are to manage the interface and communicate with the model. Action methods concerned with data displayed in a view are typically implemented in a view controller. An NSWindowController object (also part of the document architecture) is an example of a view controller.
For simple interfaces, the ViewController structure can improve development efficiency, but when complex interfaces need to be built, viewController can easily become bloated and logical.
On the other hand, Apple probably had good intentions when it came to the ViewController (VC). It was easier to write and more efficient to develop. It is true that simple pages are not a problem, but there is a big drawback is easy to put the novice into the wrong direction, think that the real MVC is to do this, resulting in many novice to the original view layer code pile to VC, such as in VC to build view, view display logic, and even in VC to initiate network requests.
This is one of the things THAT I thought was weird about VC, because it didn’t fit into any of the MVC layers, until I saw the apple documentation that VC was a combination.
Let’s talk about the responsibilities of the MVC layers under the existing iOS architecture. It should be noted that the Controller layer below refers to the VC combination in iOS
3. Responsibilities of MVC in iOS
Controller layer (VC) :
- Generate the view, and then assemble the view
- Responds to the View’s events and acts as a proxy for the View
- Call the data acquisition interface of model, get the returned data, processing, rendering to view display
- Handles the lifecycle of the view
- Handle jumps between interfaces
Model layer:
- Business logic encapsulation
- Provides data interfaces for the controller to use
- Data is stored and read persistently
- Store data as a data model
The view layer:
- Interface element building, animation effect, data display,
- Accept user actions and give visual feedback
PS: The model layer of business logic is generally and background data interaction logic, there are some abstract business logic, such as formatting date strings to the NSDateFormatter type
4, massive controller
From the MVC layer responsibilities above, you can see how much C is doing, even with a clear division of responsibilities, not to mention the fact that newcomers are piling various view and Model layer functions onto C layer.
In the complex interface inside the VC code easily more than a thousand lines, I have seen more than 5000 lines of code VC, to find a method can only rely on search, minutes want to die rhythm. The main culprit behind the massive Controller was Apple’s combination of view and Cotroller, and VC doing both view and C at the same time, resulting in a huge increase in code and a violation of MVC principles.
Here’s a simple example. Let me just say that the following examples come from this blog:
Gossip: MVC/MVP/MVVM
The quality of this article is very high, and the explanation of the three modes is quite in-depth. The key point is that there are examples to do horizontal comparison, which is not available in other articles. You can look at this article first, the demo of this article is from this article, but I have made some modifications based on my own understanding, you can compare and make your own choice.
There are also some pictures from the text, here thanks to the author ~
Let’s start with two pictures:
The interface is divided into three sections, with a personal information presentation at the top and two lists of blog and draft content below. Let’s first look at the general novice how to implement
//UserVC
- (void)viewDidLoad {
[super viewDidLoad];
[[UserApi new] fetchUserInfoWithUserId:132 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]; }}]; [[userApi new] fetchUserDraftsWithUserId:132 completionHandler:^(NSError *error, id result) { //ifError... Slightly [self drafts addObjectsFromArray: result]; [self.draftTableView reloadData]; }]; } / /... (UITableViewCell *)tableView:(UITableView *)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){
[self.navigationController pushViewController:[BlogDetailViewController instanceWithBlog:self.blogs[indexPath.row]] animated:YES];
}else{ [self.navigationController pushViewController:[draftDetailViewController instanceWithdraft:self.drafts[indexPath.row]] animated:YES]; }Copy the code
//DraftCell
- (void)setDraft:(draft)draft {
_draft = draft;
self.draftEditDate = ...
}
//BlogCell
- (void)setBlog:(Blog)blog { ... Ditto}Copy the code
model:
Blog.h
=========
#import <Foundation/Foundation.h>
@interface Blog : NSObject
- (instancetype)initWithBlogId:(NSUInteger)blogId;
@property (copy, nonatomic) NSString *blogTitle;
@property (copy, nonatomic) NSString *blogSummary;
@property (assign, nonatomic) BOOL isLiked;
@property (assign, nonatomic) NSUInteger blogId;
@property (assign, nonatomic) NSUInteger likeCount;
@property (assign, nonatomic) NSUInteger shareCount;
@end
~~~~~~~~~~~~~~~~~~~~~
blog.m
========
#import "Blog.h"
@implementation Blog
@endCopy the code
If further requirements were added, the userVC code would become more and more extensive, and this is where the massive Controller mentioned above appears. Maintainability and testability are out of the question. We are written according to Apple’s MVC architecture. Why is this a problem?
Press the table for the moment, let’s look at another problem, first to clarify this problem, for the subsequent article understanding of great benefit.
Misunderstanding of the Model layer
I see a lot of so called M-tier IMPLEMENTATIONS of MVC as shown above, with just a few dry properties. I’ve always written it this way, but I’ve always wondered, how is it possible to do this on a single layer? A data model is more like it.
So what does the correct m-layer pose look like? You can see the following specific article, for the M layer is very good, but for the MVVM understanding in the article I do not agree, everyone has different opinions
Discussion on MVVM pseudo-framework structure and M implementation mechanism in MVC
The following quotes are also from this article:
Understanding the Model layer:
The first thing to understand correctly is what is M in MVC? Is it a data model? The answer is NO. The correct definition is a business model. Is you all business data and business logic implementation should be defined inside the M layer, and the realization of the business logic and definition should have nothing to do with the concrete interface, that is, and views, and there is no relationship between the control, it can exist independently, you can even the business model separately compiled a static library to provide for the use of a third party, or other system.
This is clearly illustrated in the classic MVC diagram above: the control is responsible for calling the model, and the model sends notifications of the results to the control, which in turn informs the view to refresh. Therefore, we cannot simply understand M as a dry data model with only attributes but no methods.
In fact, it involves one of the most basic design principles, which is the basic design principle of object-oriented: what is a class? A class should be an abstraction of objects with different operations and different properties (a class is a collection of properties and methods). I don’t think there has ever been a system in which a collection of data models without methods has been defined as a single, abstract model layer for you to use. We cannot treat a folder that holds the data model as a layer, this does not conform to horizontal sharding rules.
Correct posture for the Model layer implementation:
The code in the M layer is defined to be completely independent of the V and C layers, i.e. the objects in the M layer do not depend on any C or V layer objects. The optimal design structure of the whole framework is that layer V does not depend on layer C but exists independently, layer M does not depend on layer C and layer V. Layer C is responsible for associating the two, layer V is only responsible for presentation, layer M holds the specific implementation of data and business, while layer C handles event response, business invocation and notification interface update. The three must be clearly defined as unidirectional dependence, and should not appear bidirectional dependence
The M layer needs to complete the encapsulation of the business logic. Generally, the business logic involves the business interaction between the client and the server. The M layer encapsulates all the business details of the network protocol used (HTTP, TCP, others), the data format used to interact with the server (XML, JSON, others), the local cache and the database storage (COREDATA, SQLITE, others), and none of this is exposed to the C layer. All that is called by the C layer is implemented by the member methods provided by each business class in the M layer. This means that layer C does not need to know and should not know any protocols used to communicate with clients and servers, data packet formats, and storage aspects. The benefit of this is that changes to the communication protocol between the client and server, data format, and local storage will not affect any overall application framework, because the interface provided to layer C will remain unchanged, and only the code for layer M will be upgraded and updated. For example, if we want to change the network request library from ASI to AFN, we just need to change it at the M layer. The entire C layer and V layer code will not change.
The article also gives an example of the implementation, so I’m not going to paste it, but you can go and see it
In conclusion:
The M layer should not be a data model, just a few attributes. It should be a layer of responsibility that carries the business logic and data store retrieval.
6. How to build the right MVC
Now let’s look at how to build a proper MVC under iOS.
First, agree that the ViewController is not a C layer, but a mixture of the V and C layers. As we can see in a standard iOS MVC implementation, the C layer does most of the work and is divided into five parts (see MVC layer responsibilities above). Since it is a mixture of the two layers, to reduce VC’s burden, we now use VC only as a view container.
Here I want to explain what is called a view container. We know that apple VC has a self.view, and all views to be displayed on the top of the interface must be added to the root view via addSubView. VC also controls the life cycle of the view. So can we think of VC as a container that manages views?
You can read this article to further understand the concept of view Container I mentioned above:
IOS Application Architecture: How to organize and invoke the View layer
At this point, VC’s responsibilities are simplified to the following three:
- Generate a child view and add it to your own self.view
- Manage the lifecycle of the View
- Notify each subC to fetch the data
The first two points are easy to understand, because they’ve already been covered. Third, let’s move on
The vanishing layer C
Going back to our fourth example, what causes VC code to become bloated? Because we clearly demarcated the responsibilities of view and Model layer, the former was responsible for data presentation, while the latter was responsible for data acquisition, those ambiguous code felt inappropriate in these two layers, so they were thrown into VC, which led to VC’s increasing expansion.
At this point, the code organization is shown in the figure below:
Through this picture can be found, the user information page (userVC) as a business scenario Scene needs to display a variety of data M (Blog/Draft/the UserInfo), so the corresponding with multiple View (blogTableView/draftTableView/image…). However, there is no connection layer C between each MV, and all the logic that should be distributed to each LAYER of C is packaged and thrown to Scene(userVC) for processing, that is, M-C-V becomes MM… – Scene -… VV, layer C just disappeared for no apparent reason.
In addition, the two V cells are directly coupled to M(blog/ Draft), which means that the input of the two V’s is tied to the corresponding M, and reuse is impossible.
Finally, the test exception for this business scenario is troublesome, because the business initialization and destruction are bound to the VC life cycle, and the corresponding logic is also associated with the View click event, the test can only Command+R, dot dot…
So how do you do the right MVC?
As shown in the figure below, the information on the interface is divided into three parts: personal information, blog list information, draft list information. We should also divide the three parts into three smaller MVCS, and then assemble the three sub-Mvcs through VC splicing to complete the entire interface.
The specific code organization structure is as follows:
As a business scenario, UserVC needs to display three kinds of data, corresponding to which there are three MVC’s. These three MVC’s are responsible for data acquisition, data processing and data presentation of their respective modules. What UserVC needs to do is to configure these three MVC’s well, and inform their RESPECTIVE C layer to acquire data at an appropriate time. After each C layer gets the data, it will process the data accordingly. After the processing is completed, it will be rendered to their respective views. UserVC will finally layout the rendered views
See the MVC folder in the final demo for specific code. About the demo code, I want to explain a bit of their own views: in the demo network data acquisition, the author put a separate file UserAPIManager inside. In my opinion, it is better to put it in the demo related to business, because once there are more interfaces, a file is easy to expand. If you divide it into multiple files according to business, it is better to put it in the Model to be clearer.
PS:
The blogTableViewHelper in the figure corresponds to the blogTableViewController in the code, as do the other helpers
At this point, userVC as VC only needs to do three things:
- Generate a child view and add it to your own self.view
- Manage the lifecycle of the View
- Notify each subC to fetch the data
The code of userVC is greatly reduced, and the logic is clearer at this time, and because the presentation and interaction of each module is self-managed, userVC only needs to be responsible for the parts strongly related to its own business.
In addition, if you need to show the blog list data in another VC, then you only need to add the view of the blog list to the VC view, and then get the data through the controller of the blog list, so that the purpose of reuse.
We divide the code in userVC into three sub-MVC through the above method, so that the architecture is clearer and clearer. For more complex pages, we can do more detailed decomposition, and at the same time, each sub-MVC can actually be divided into more detailed MVC. The specific granularity of the split we see page complexity flexible, if it is expected that the business logic of a page will continue to increase, it is better to split into different sub-MVC to implement at the beginning. If it’s a simple page, it’s ok to write all the logic in the VC.
7. Advantages and disadvantages of MVC
advantages
The MVC transformation above is mainly to distinguish VC and C, let MVC become a real MVC, rather than let VC as C to use, after the transformation of MVC to deal with the general scene should be more than enough. No matter how complex the interface is, you can break it down into smaller MVCS and put it back together.
Writing code is a constant process of refactoring, and as the project gets bigger, individual features can be taken out as one big module and packaged into a POD library (this is a componentization point, and I’ll blog about it later). At the same time within the module you can split it in layers. Strive for a single principle, don’t heap everything in one class
The advantages of MVC are summarized as follows:
- Code reuse: The V(Cell /userInfoView) of the three small modules only exposes the Set method externally, and is isolated to M and even C, so there is no problem with reuse. MVC with three large modules can also be used to quickly build similar business scenarios (larger modules are less reusable than smaller ones, as I’ll explain below).
- Code bloat: Since most of the logic and layout of the Scene have been transferred to the corresponding MVC, we just built two different business scenarios by assembling MVC. Each business scenario can normally carry out the corresponding data display and the corresponding logical interaction, and it only takes about 100 lines of code to complete these things (of course, I’m ignoring the layout code for the Scene here.
- Extensibility: No matter the product wants to add recycle bin or defense tower in the future, all I need is to create the corresponding MVC module and add it to the corresponding Scene.
- Maintainability: responsibilities are separated between modules, and what goes wrong can be changed without affecting other modules at all. In addition, the code of each module is actually not too much, even if the person who wrote the code leaves one day, the person who took over the code can quickly locate the error module according to the error prompt.
- Tetestability: Unfortunately, the initialization of the business is still bound to the Scene lifecycle, and some logic still needs to be triggered by UI click events. We can only Command+R, dot dot…
disadvantages
After the above transformation, the MVC architecture is clear enough, according to the application scenario (generally single page) to be broken down into large, and then according to the business into small MVC. If you can’t, just take it off, take it off, take it off.
But the big problem with MVC is that YOU can’t reuse the C code, so if you can take it out of the C layer, let’s take a look at what’s in the C layer now
- As the intermediary between View and Model, it obtains data from Model, processes data and renders it to View for display
- Responds to the click event of the View and executes the appropriate business logic
- Acts as a proxy and data source for the View
- Expose the interface to SceneVC to drive itself to get data
This leads to a question:
Strong coupling between business logic and business presentation: As you can see, some of the business logic (page jump/like/share…) Is directly scattered in the V layer, which means that when we test this logic, we must first generate the corresponding V before we can test it. Obviously, this is not reasonable. Since the business logic ultimately changes data M, our focus should be on M, not V that shows M
For example, for example, the like function code in the demo is as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BlogCellHelper *cellHelper = self.blogs[indexPath.row];
BlogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier]; cell.title = cellHelper.blogTitleText; cell.summary = cellHelper.blogSummaryText; cell.likeState = cellHelper.isLiked; cell.likeCountText = cellHelper.blogLikeCountText; cell.shareCountText = cellHelper.blogShareCountText; Weak Typeof (cell) weakCell = cell; weak Typeof (cell) weakCell = cell; [cellsetDidLikeHandler:^{
if (cellHelper.blog.isLiked) {
[self.tableView showToastWithText:@"You already liked it."];
} else {
[[UserAPIManager new] likeBlogWithBlogId:cellHelper.blog.blogId completionHandler:^(NSError *error, id result) {
if (error) {
[self.tableView showToastWithText:error.domain];
} else{ cellHelper.blog.likeCount += 1; cellHelper.blog.isLiked = YES; Weakcell.likestate = cellHelper.blog.isliked; weakcell.likeState = cellHelper.blog.isliked; weakCell.likeCountText = cellHelper.blogTitleText; }}]; }}];return cell;
}Copy the code
It can be clearly seen from the code that the cell must be generated and then click the “like” button on the cell to trigger the “like” business logic. But the business logic generally changes the model data, and the View just gets the model data for presentation. Now you have these two separate things together. The business logic cannot be tested separately.
The MVP mentioned below was born to solve this problem, so let’s take a look.
MVP
A Brief Introduction to THE MVC, MVP, and MVVM Architectural Patterns
1, an overview of the
The downside of MVC is that it doesn’t distinguish between business logic and business presentation, which is not friendly to unit testing. MVP is optimized for the above shortcomings by separating the business logic from the business presentation, which becomes MVCP.
The M and V functions remain the same, the original C is now only responsible for layout, and all the business logic has been transferred to the P layer. The P layer processes the business logic, and if you want to change the display of the View, you can do so through a callback, which reduces the coupling and allows you to test the P layer business logic separately
There are many variants and definitions of MVP, but the most widely known is the Presentation Model published by Martin Fowler, which is the MVP that will be introduced below. Take a look at the following article:
Presentation Model by Martin Fowler
MVP separates behavior (event responses) and state (properties, which are used for data presentation) from the view layer. It creates an abstraction of the view, the Presenter layer, and the view is the “rendered” result of the P layer. The P layer contains all the dynamic information required for rendering the view, including the view’s content (text, color), whether the component is enabled (enable), and exposes methods to the view to respond to certain events.
2. MVP architecture and comparison of responsibilities
The architecture of the MVP is shown below:
In MVP, a Presenter can be understood as a loose controller that contains the UI business logic of the view. All events from the view are represented to the Presenter. The Presenter also communicates with the view through its exposed interface.
The layers of responsibility are as follows
VC layer
- Layout and assembly of the view
- View lifecycle control
- Notify each P layer to fetch the data and render it to the view for display
The controller layer
- Generate view, implement view proxy and data source
- Bind a View to a Presenter
- Call the Presenter to execute the business logic
Model layer
- Similar to the MODEL layer of MVC
The view layer
- Listen for data update notifications from layer P and refresh the page display.
- When the click event is triggered, the corresponding method of the P layer is called and the result of the method execution is displayed. (In MVC, the C layer is responsible for this.)
- Layout and animation of interface elements
- Feedback user operations
Presenter layer of responsibility
- Implement the view’s event handling logic, exposing the corresponding interface to the view’s event invocation
- Call the Model interface to get the data, and then process the data, encapsulating it into data and state that a View can directly display
- Handle the jump between interfaces (this depends on the actual situation in P or C)
Let’s analyze the responsibilities of View layer. 3 and 4 are similar to MVC View, but 1 and 2 are different, mainly because the business logic is transferred from C to P, so the View’s event response and state change must depend on P to implement.
There are two different implementations:
- Let P hold V, P changes the display data and state of V through V’s exposed interface, and P executes its own business logic through V’s event callback
- Let V hold P, V changes its display data and state through the proxy callback of P, and V directly calls the interface of P to execute the business logic corresponding to the event response
The first approach keeps the view pure, presenting data and changing state as a passive view, but causes P to couple V, so that the business logic and presentation are mixed together, just like the MVC above.
The second way ensures the purity of P, and lets P only do business logic. As for the change of data display caused by business logic, lets view implement the corresponding agent event to realize it. This increases the complexity of the view and the coupling of the view to P.
Demo uses the second method, but the view in Demo depends on a concrete presenter. If a view has multiple presenters, consider abstracting the methods and properties exposed by presenters into a protocol. Make the View dependent on the abstraction rather than the implementation.
3. MVP of passive diagram mode
2. The most common MVP architectural models today are variants of this: Passive View and beast Controller. Let’s start with the first one, the one that’s used a lot
The first major variant of MVP is the Passive View; As the name implies, in this variant of the architectural pattern, the view layer is passive, it does not change any state of itself, it just defines the style and layout of the price, there is no logic of its own.
It then exposes interfaces through which the external world renders data to the View for display, and all state is changed indirectly through the Presenter (usually by a proxy that implements the Presenter in the view). In this way, the view can be reused to the maximum extent and testability is greatly improved
See Passive View
Communication mode
- When the view receives an event from a user, it forwards the event to the Presenter for processing.
- The passive view implements the presentr proxy. The Presenter calls back to the proxy to update the contents of the view when the view needs to be updated. This allows the Presenter to focus on the business logic and the View on the display logic
- A Presenter is responsible for manipulating and updating the model, fetching information from it as needed.
- When the model layer changes, it can send the changed information to the observer Presenter.
4. Oversee the MVP of controller mode
In the supervisory controller, the view layer takes over some of the view logic, mainly synchronizing the state of the simple view and model; The supervisory controller is responsible for responding to user input and some of the more complex synchronization of views and model states.
The supervisory controller handles user input in exactly the same way as a standard MVP Presenter. However, for data synchronization between views and models, a bidirectional binding mechanism similar to the one in MVVM is used to map the two to each other.
As shown in the figure below:
The increased coupling between the view and model layers in the supervisory controller increases the complexity of the overall architecture. Unlike the MVP of passive diagrams, there are new dependencies between views and models, namely bidirectional data binding. Views are bound to simple properties in the model using a declarative syntax, and when the model changes, its observers are notified that the view is updated accordingly.
In this way, it can reduce the burden of supervising the controller, reduce the simple code in it, and leave part of the logic to the view for processing; As a result, views can be updated by both presenters and data bindings, and the supervised controller reduces the testeability and encapsulation of views compared to passive views.
2. Refer to the article Supervising Controller
5. How to build the right MVP
The downside of MVC is that it doesn’t distinguish between business logic and business presentation, which is not friendly to unit testing. MVP is optimized for the above shortcomings by separating the business logic from the business presentation, which becomes MVCP. The functions of M and V remain unchanged. The original C is only responsible for generating and acting as the agent of the View (the layout of the view is still completed by SceneVC), and all the business logic is transferred to the P layer.
Let’s refactor the above interface with MVP, and the architecture is as follows:
There is no change in the business scenario, which still shows three kinds of data, but the three MVC’s are replaced by the three MVPS (I only draw the Blog module in the figure), UserVC is responsible for configuring the three MVPS (create their respective VP, establish C through VP, C will be responsible for establishing the binding relationship between VP), And notifying their respective P layer (previously C layer) for data acquisition when appropriate.
After the processing is completed, it will inform the bound View that the data is updated. After receiving the update notification, V will obtain the formatted data from P for page rendering. UserVC will finally layout all the rendered views.
In addition, layer V and Layer C no longer process any business logic, and all events trigger all calls to the corresponding commands of layer P.
Concrete code you see the demo of the line, below I draw out the thumbs up function to compare the analysis of MVC and MVP implementation of what is different
MVP thumbs-up code
Blogviewcontroller.m // like event - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { BlogViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier]; cell.presenter = self.presenter.allDatas[indexPath.row]; //PV binding __weak Typeof (cell) weakCell = cell; [cellsetDidLikeHandler:^{
[weakCell.presenter likeBlogWithCompletionHandler:^(NSError *error, id result) { ! error ? : [weakCell showToastWithText:error.domain]; }]; }];return cell;
}
==========================================
BlogCellPresenter.m
- (void)likeBlogWithCompletionHandler:(NetworkCompletionHandler)completionHandler {
if(self.blog.isLiked) { ! completionHandler ? : completionHandler([NSError errorWithDomain:@"You already liked it." code:123 userInfo:nil], nil);
} else{ BOOL response = [self.view respondsToSelector:@selector(blogPresenterDidUpdateLikeState:)]; self.blog.isLiked = YES; self.blog.likeCount += 1; ! response ? : [self.view blogPresenterDidUpdateLikeState:self]; [[UserAPIManager new] likeBlogWithBlogId:self.blog.blogId completionHandler:^(NSError *error, id result) {
if(error) { self.blog.isLiked = NO; self.blog.likeCount -= 1; ! response ? : [self.view blogPresenterDidUpdateLikeState:self]; }! completionHandler ? : completionHandler(error, result); }]; } } ========================================== BlogViewCell.m
#pragma mark - BlogCellPresenterCallBack
- (void)blogPresenterDidUpdateLikeState:(BlogCellPresenter *)presenter {
[self.likeButton setTitle:presenter.blogLikeCountText forState:UIControlStateNormal];
[self.likeButton setTitleColor:presenter.isLiked ? [UIColor redColor] : [UIColor blackColor] forState:UIControlStateNormal];
}
- (void)blogPresenterDidUpdateShareState:(BlogCellPresenter *)presenter {
[self.shareButton setTitle:presenter.blogShareCountText forState:UIControlStateNormal];
}
#pragma mark - Action- (IBAction)onClickLikeButton:(UIButton *)sender { ! self.didLikeHandler ? : self.didLikeHandler(); }#pragma mark - Setter
- (void)setPresenter:(BlogCellPresenter *)presenter {
_presenter = presenter;
presenter.view = self;
self.titleLabel.text = presenter.blogTitleText;
self.summaryLabel.text = presenter.blogSummaryText;
self.likeButton.selected = presenter.isLiked;
[self.likeButton setTitle:presenter.blogLikeCountText forState:UIControlStateNormal];
[self.shareButton setTitle:presenter.blogShareCountText forState:UIControlStateNormal];
}Copy the code
MVC likes
blogViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
BlogCellHelper *cellHelper = self.blogs[indexPath.row];
BlogTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ReuseIdentifier]; cell.title = cellHelper.blogTitleText; cell.summary = cellHelper.blogSummaryText; cell.likeState = cellHelper.isLiked; cell.likeCountText = cellHelper.blogLikeCountText; cell.shareCountText = cellHelper.blogShareCountText; Weak Typeof (cell) weakCell = cell; weak Typeof (cell) weakCell = cell; [cellsetDidLikeHandler:^{
if (cellHelper.blog.isLiked) {
[self.tableView showToastWithText:@"You already liked it."];
} else {
[[UserAPIManager new] likeBlogWithBlogId:cellHelper.blog.blogId completionHandler:^(NSError *error, id result) {
if (error) {
[self.tableView showToastWithText:error.domain];
} else{ cellHelper.blog.likeCount += 1; cellHelper.blog.isLiked = YES; Weakcell.likestate = cellHelper.blog.isliked; weakcell.likeState = cellHelper.blog.isliked; weakCell.likeCountText = cellHelper.blogTitleText; }}]; }}];return cell;
}
===========================================
BlogViewCell.m - (IBAction)onClickLikeButton:(UIButton *)sender { ! self.didLikeHandler ? : self.didLikeHandler(); }#pragma mark - Interface
- (void)setTitle:(NSString *)title {
self.titleLabel.text = title;
}
- (void)setSummary:(NSString *)summary {
self.summaryLabel.text = summary;
}
- (void)setLikeState:(BOOL)isLiked {
[self.likeButton setTitleColor:isLiked ? [UIColor redColor] : [UIColor blackColor] forState:UIControlStateNormal];
}
- (void)setLikeCountText:(NSString *)likeCountText {
[self.likeButton setTitle:likeCountText forState:UIControlStateNormal];
}
- (void)setShareCountText:(NSString *)shareCountText {
[self.shareButton setTitle:shareCountText forState:UIControlStateNormal];
}Copy the code
As you can see from the code comparison above, MVP has twice as much code as MVC, but MVP has a clearer hierarchy. The business logic and presentation are completely separated so that presenter and View can be tested separately, whereas MVC mixes the two together and cannot be tested separately. In the actual project, you can make your own choices according to the project requirements.
Here’s the like logic of MVC
Weak Typeof (cell) weakCell = cell; weak Typeof (cell) weakCell = cell; [cellsetDidLikeHandler:^{
if (cellHelper.blog.isLiked) {
[self.tableView showToastWithText:@"You already liked it."];
} else {
[[UserAPIManager new] likeBlogWithBlogId:cellHelper.blog.blogId completionHandler:^(NSError *error, id result) {
if (error) {
[self.tableView showToastWithText:error.domain];
} else{ cellHelper.blog.likeCount += 1; cellHelper.blog.isLiked = YES; Weakcell.likestate = cellHelper.blog.isliked; weakcell.likeState = cellHelper.blog.isliked; weakCell.likeCountText = cellHelper.blogTitleText; }}]; }}];Copy the code
You can see that the business logic (changing the Model data) and the business presentation (changing the cell data) are mixed together. If I want to test the business logic of clicking like, I have to generate the cell and then click on the cell button to trigger the business logic of clicking like
Look at the implementation of the like logic under MVP
Service logic: BlogCellPresenter.m
- (void)likeBlogWithCompletionHandler:(NetworkCompletionHandler)completionHandler {
if(self.blog.isLiked) { ! completionHandler ? : completionHandler([NSError errorWithDomain:@"You already liked it." code:123 userInfo:nil], nil);
} else{ BOOL response = [self.view respondsToSelector:@selector(blogPresenterDidUpdateLikeState:)]; self.blog.isLiked = YES; self.blog.likeCount += 1; ! response ? : [self.view blogPresenterDidUpdateLikeState:self]; [[UserAPIManager new] likeBlogWithBlogId:self.blog.blogId completionHandler:^(NSError *error, id result) {
if(error) { self.blog.isLiked = NO; self.blog.likeCount -= 1; ! response ? : [self.view blogPresenterDidUpdateLikeState:self]; }! completionHandler ? : completionHandler(error, result); }]; }}Copy the code
Business display: BlogViewCell.m
#pragma mark - BlogCellPresenterCallBack
- (void)blogPresenterDidUpdateLikeState:(BlogCellPresenter *)presenter {
[self.likeButton setTitle:presenter.blogLikeCountText forState:UIControlStateNormal];
[self.likeButton setTitleColor:presenter.isLiked ? [UIColor redColor] : [UIColor blackColor] forState:UIControlStateNormal];
}
- (void)blogPresenterDidUpdateShareState:(BlogCellPresenter *)presenter {
[self.shareButton setTitle:presenter.blogShareCountText forState:UIControlStateNormal];
}Copy the code
You can see that in MVP, the business logic and the business presentation are implemented in different places, so you can test the two separately, instead of testing the business logic like MVC, you have to generate a view, which makes no sense, because the business logic changes the data of the Model, which has nothing to do with the view.
MVP, as opposed to MVC, does only one thing, which is to separate business presentation and business logic. After the separation of display and logic, as long as we can ensure that V can normally refresh the page after receiving the data update notification from P, there will be no problem with the whole business. Because the notifications V receives are actually from the data acquisition/update operations of the P layer, we only need to ensure that these operations of the P layer are normal. That is, we only test the logic of the P layer, and we don’t care about the V layer
MVVM
1, an overview of the
MVVM was originally proposed by Microsoft, but the architecture was also developed based on the following article:
Presentation Model by Martin Fowler
As mentioned above in this article, MVVM is the prototype of MVP, which means that MVVM is actually based on MVP. So what does MVVM improve on MVP? The answer is data binding, which will be rolled out next. There are too many definitions of MVVM on the Internet, without a unified statement, and some are even completely opposite. For an authoritative MVVM explanation, you can see the official Microsoft document:
The MVVM Pattern
The motivation, pain points and responsibilities of each layer of MVVM are clearly explained. For a closer look at MVVM’s past and present, check out Martin Fowler’s article above
In 2005, John Gossman published Introduction to Model/View/ViewModel Pattern for Building WPF Apps on his blog. MVVM is exactly the same as the PM mode mentioned by Martin Fowler. Fowler’s PM mode is a platform-independent method of creating view abstractions, while Gossman’s MVVM is a mode dedicated to the WPF framework to simplify the creation of user interfaces. We can think of MVVM as an implementation of the PM pattern on the WPF platform.
From the name model-view-viewModel, it consists of three parts: Model, View, and ViewModel; The ViewModel is actually the P in MVP mode, which is called VM in MVVM.
MVVM Architecture diagram:
In addition to the familiar Model, View, and ViewModel sections, MVVM introduces an implicit Binder layer that is an improvement over MVVM’s MVP. The declarative binding of data and commands is done with the Binder layer in MVVM mode. RAC is an elegant implementation of the binder layer for iOS, and MVVM can run without RAC.
The following figure shows how MVC is broken down into MVVM under iOS:
The biggest improvement of MVVM and MVP over MVC is that P or VM creates an abstraction of the view, and pulls out the state and behavior in the view to form a new abstraction. This allows the business logic (P/VM) and business presentation (V) to be tested separately and for reuse purposes, making the logical structure clearer
2. Responsibilities of each layer of MVVM
MVVM’s responsibilities are similar to MVP’s. VM corresponds to THE P layer, but the View layer of MVVM has more data binding operations
3. MVVM improvements over MVP
As mentioned above, MVVM has a bidirectional data and command binding between VM/P and view. What are the benefits of this? Look at the MVP “like” example above
MVP’s like logic is as follows:
Click the “cell” button –> call P’s “like” logic —-> after successful “like”, P changes the data of M –>P calls back to the agent method of the cell to change the display of the cell (successful “like”, the number of “like” is increased by 1, and the number of “like” is red, otherwise the number of “like” is not changed or changed)
So that’s the whole event process, and you can see that it’s done in four steps, and each time you have to synchronize the state of P to the view, and when you have a lot of events, it’s a lot of trouble to write this. Is there a simple mechanism to synchronize the behavior and state of the view with the behavior and state of the P?
The answer is MVVM’s binder mechanism.
Look at the MVP section above. Let’s look at how MVVM likes are implemented:
BlogCellViewModel.h
- (BOOL)isLiked;
- (NSString *)blogTitleText;
- (NSString *)blogSummaryText;
- (NSString *)blogLikeCount;
- (NSString *)blogShareCount;
- (RACCommand *)likeBlogCommand;
========================================
BlogCellViewModel.m
@weakify(self);
self.likeBlogCommand = [[RACCommand alloc] initWithSignalBlock:^RACSignal *(id input) {
@strongify(self);
RACSubject *subject = [RACSubject subject];
if (self.isLiked) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
self.isLiked = NO;
self.blogLikeCount = self.blog.likeCount - 1;
[subject sendCompleted];
});
} else {
self.isLiked = YES;
self.blogLikeCount = self.blog.likeCount + 1;
[[UserAPIManager new] likeBlogWithBlogId:self.blog.blogId completionHandler:^(NSError *error, id result) {
if (error) {
self.isLiked = NO;
self.blogLikeCount = self.blog.likeCount - 1;
}
error ? [subject sendError:error] : [subject sendCompleted];
}];
}
return subject;
}];Copy the code
- (void)awakeFromNib { [super awakeFromNib]; // Data binding @weakify(self); RAC(self.titleLabel, text) = RACObserve(self, viewModel.blogTitleText); RAC(self.summaryLabel, text) = RACObserve(self, viewModel.blogSummaryText); RAC(self.likeButton, selected) = [RACObserve(self, viewModel.isLiked) ignore:nil]; [RACObserve(self, viewModel.blogLikeCount) subscribeNext:^(NSString *title) { @strongify(self); [self.likeButtonsetTitle:title forState:UIControlStateNormal];
}];
[RACObserve(self, viewModel.blogShareCount) subscribeNext:^(NSString *title) {
@strongify(self);
[self.shareButton setTitle:title forState:UIControlStateNormal]; }]; } - (IBAction)onClickLikeButton (UIButton *)sender {// Event responseif(! self.viewModel.isLiked) { [[self.viewModel.likeBlogCommand execute:nil] subscribeError:^(NSError *error) {
[self showToastWithText:error.domain];
}];
} else {
[self showAlertWithTitle:@"Tip" message:@"Sure you want to unlike it?" confirmHandler:^(UIAlertAction *confirmAction) {
[[self.viewModel.likeBlogCommand execute:nil] subscribeError:^(NSError *error) { [self showToastWithText:error.domain]; }]; }]; }}Copy the code
You can see that the view relative to MVP triggers P’s business logic, and P then calls back to change the display of the view, using MVVM data binding to make the logic clearer and less code. This is where MVVM improves over MVP
VIPER
1, an overview of the
Most of the previous architectures were born out of MVC, but VIPER has nothing to do with MVC and is a completely new architecture. It can be seen from one point: the first few MVX frameworks under iOS are unable to get rid of the influence of Apple’s ViewController, but VIPER completely weakened the concept of VC, VC into a real sense of View. The VC responsibilities have been completely split up and divided into various sub-layers. Here is the architecture diagram of VIPER
As you can see from the above, VIPER is probably the most clear delineation of responsibilities among all the architectures, and truly adheres to the SOLID principles. Other architectures are more or less ambiguous because of the presence of VC. However, because VIPER has too many layers and is the only one that separates the interface routing function into a separate class, all the event responses and interface jumps have to be handled by themselves, resulting in a significant increase in code complexity.
Apple worked hard to give us a VC, although it will lead to hierarchical coupling, but also really simplify the development process, and VIPER is completely abandoned VC, re-layered, so that each module can be tested and reused separately, but also led to too much code, logic more around the problem.
In my experience, the MVC architecture is good enough for most scenarios, provided it is layered and planned. Some articles have said that MVVM is to solve the C layer bloat, MVC is hard to test, but it is not. In terms of architecture evolution order, most of the C layer bloat is not split MVC module well, well split on the line, without MVVM. While MVC is difficult to test, it can also be solved with MVP, but MVP is not perfect, the data interaction between VP is too tedious, so MVVM is introduced. VIPER, on the other hand, is breaking out of the MVX architecture and blazing a new trail on its own.
VIPER is a very clean architecture. It isolates each module from the others. Therefore, changing or fixing a bug is very simple, because you only need to update specific modules. In addition, VIPER creates a great environment for unit testing. Since each module is independent of the others, the low coupling is maintained. It is also easy to divide the work between developers. You shouldn’t use VIPER on small projects, because MVP or MVC will suffice
As to whether you should use VIPER in your projects, check out the Quora discussion below:
Should I use Viper architecture for my next iOS application, or it is still very new to use?
2. Responsibilities at all levels of VIPER
- Interactor – This is the backbone of the application because it contains the business logic of the application that is described by the cases. The interlocutor is responsible for fetching data from the data layer and executing the business logic for a particular scenario, and its implementation is completely independent of the user interface.
- Presenter – It retrieves data from the Interactor the user operates on, creates an entity instance, and passes it to the View to display it.
- Entities – pure data objects. The data access layer is not included, as this is the Interactor’s responsibility.
- Router – Switches between VIPER modules
- View – It is the responsibility of the View to send user actions to the presenter and to display whatever the Presenter tells it
PS: Data retrieval should be placed in a separate layer, not in Interactor
You can see that all the function points of an application scenario are separated into completely separate layers, each with a single responsibility. In the VIPER architecture, each block corresponds to an object that has a specific task, input, and output. It is very similar to a worker on an assembly line: once a worker completes a job on his or her object, that object is passed to the next worker until the product is complete.
The connections between layers represent the relationships between objects and the types of information they pass to each other. Communication from one entity to another is given through protocols.
The idea behind this architectural pattern is to isolate application dependencies and balance the allocation of responsibilities between entities. Basically, the VIPER architecture divides your application logic into smaller layers of functionality, each with strict predetermined responsibilities. This makes it easier to test the interaction of boundaries between layers. It is suitable for unit testing and makes your code more reusable.
3. Key advantages of the VIPER architecture
- Simplify complex projects. Because of module independence, VIPER is really good for large teams.
- Make it scalable. Make it as seamless as possible for developers to work on it simultaneously
- The code achieves reusability and testability
- Divide application components according to their roles and set clear responsibilities
- New features can be easily added
- Because your UI logic is separate from your business logic, you can easily write automated tests
- It encourages separation and makes it easier to adopt TDD concerns. Interactor contains pure logic independent of any UI, which makes it easy to drive through tests
- Create clear, unambiguous interfaces that are independent of other modules. This makes it easier to change the way the interface presents various modules to the user.
- The single responsibility principle makes it easier to track problems through crash reports
- Make source code cleaner, more compact, and reusable
- Reduce the number of conflicts within development teams
- Apply SOLID principle
- Make the code look similar. Reading other people’s code is faster.
The VIPER architecture has many benefits, but it is important to use it for large and complex projects. Because of the number of elements involved, this architecture incurs overhead when starting new small projects, so the VIPER architecture can have too much impact on small projects that are not intended to expand. Therefore, for a project like this, it’s better to use something else, such as MVC.
How to build the right VIPER
We’re going to build a little VIPER application, and I don’t want to rewrite that demo in VIPER because it’s too much trouble, so I’m going to write a simple demo to show you how VIPER works, but it’s got all the functionality it needs.
As shown in the figure above, there are two interfaces: ContactList and AddContact. Click the Add button in the upper right corner of The ContactList to jump to the AddContact interface. Enter firstName and secondName and click the Done button. Back on the ContactList interface, the newly added user is displayed on the interface.
Take a look at the architecture of the project, which looks like this:
As you can see, each interface has six folders, as well as two Entities folders that are common to the interface. Each folder corresponds to a layer. In addition to VIPER’s five layers, each interface has two folders: the Protocols and DataManager layers.
Protocols define the Protocols that each layer of VIPER must follow, and the operations exposed by each layer are abstracted by Protocols, allowing you to program against abstractions. DataManager defines data operations, including retrieving and storing data from local and network sources.
Let’s take a look at the Protocols class implementation:
import UIKit
/**********************PRESENTER OUTPUT***********************/
// PRESENTER -> VIEW
protocol ContactListViewProtocol: class {
var presenter: ContactListPresenterProtocol? { get set }
func didInsertContact(_ contact: ContactViewModel)
func reloadInterface(with contacts: [ContactViewModel])
}
// PRESENTER -> router
protocol ContactListRouterProtocol: class {
static func createContactListModule() -> UIViewController
func presentAddContactScreen(from view: ContactListViewProtocol)
}
//PRESENTER -> INTERACTOR
protocol ContactListInteractorInputProtocol: class {
var presenter: ContactListInteractorOutputProtocol? { get set }
var localDatamanager: ContactListLocalDataManagerInputProtocol? { get set }
func retrieveContacts()
}
/**********************INTERACTOR OUTPUT***********************/
// INTERACTOR -> PRESENTER
protocol ContactListInteractorOutputProtocol: class {
func didRetrieveContacts(_ contacts: [Contact])
}
//INTERACTOR -> LOCALDATAMANAGER
protocol ContactListLocalDataManagerInputProtocol: class {
func retrieveContactList() throws -> [Contact]
}
/**********************VIEW OUTPUT***********************/
// VIEW -> PRESENTER
protocol ContactListPresenterProtocol: class {
var view: ContactListViewProtocol? { get set }
var interactor: ContactListInteractorInputProtocol? { get set }
var wireFrame: ContactListRouterProtocol? { get set }
func viewDidLoad()
func addNewContact(from view: ContactListViewProtocol)
}Copy the code
In fact, from this class you can clearly see the data flow between the VIPER layers, very clear. And then the layers are going to implement these protocols, so I’m not going to post the code here, but you can go to the demo. The following is mainly about the routing layer, which is unique to VIPER. Other MVX architectures put routing into VC, while VIPER architecture completely abandoned VC, so the routing between interfaces to do a separate layer.
So let’s see
ContactListRouter
import UIKit class ContactListRouter: ContactListRouterProtocol {/ / generates ContactList View class func createContactListModule () - > UIViewController {let navController = mainStoryboard.instantiateViewController(withIdentifier: "ContactsNavigationController")
if let view = navController.childViewControllers.first as? ContactListView {
let presenter: ContactListPresenterProtocol & ContactListInteractorOutputProtocol = ContactListPresenter()
let interactor: ContactListInteractorInputProtocol = ContactListInteractor()
let localDataManager: ContactListLocalDataManagerInputProtocol = ContactListLocalDataManager()
letrouter: ContactListRouterProtocol = ContactListRouter () / / bind 9 layers view. The presenter = presenter presenter. View = the view presenter.wireFrame = router presenter.interactor = interactor interactor.presenter = presenter interactor.localDatamanager =localDataManager
return navController
}
returnUIViewController()} // Navigate to the AddContact interface func presentAddContactScreen(from View: ContactListViewProtocol) {guardlet delegate = view.presenter as? AddModuleDelegate else {
return
}
let addContactsView = AddContactRouter.createAddContactModule(with: delegate)
if let sourceView = view as? UIViewController {
sourceView.present(addContactsView, animated: true, completion: nil)
}
}
static var mainStoryboard: UIStoryboard {
return UIStoryboard(name: "Main", bundle: Bundle.main)
}
}Copy the code
ContactListRouter has three functions:
- Generate a ContactList view
- Binding VIPER layers in the ContactList scenario
- Route to the AddContact interface
The first function is called by the APPDelegate:
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
letcontactsList = ContactListRouter.createContactListModule() window = UIWindow(frame: UIScreen.main.bounds) window? .rootViewController = contactsList window? .makeKeyAndVisible()return true
}Copy the code
The second function is called by clicking the add button in the upper right corner of the ContactList interface:
class ContactListView: UIViewController { var presenter: ContactListPresenterProtocol? @ibAction Func didClickOnAddButton(_ Sender: UIBarButtonItem) {Presenter? .addNewContact(from: Self)}} ================= // Presenter implement the add button, call the router to the AddContact interface class ContactListPresenter: ContactListPresenterProtocol { weak var view: ContactListViewProtocol? var interactor: ContactListInteractorInputProtocol? var router: ContactListRouterProtocol? func addNewContact(from view: ContactListViewProtocol) { router? .presentAddContactScreen(from: view) } }Copy the code
The same goes for AddContact’s Router layer, which you’ll see for yourself. From the above code, we can see that the most important feature of the VIPER architecture is the implementation of the SOLID principle. Each layer only does its own thing, and the division of responsibilities is very clear. When its own tasks are finished, it will be handed over to the next layer.
After reading the above code, do you think this is too convoluted? Yes, I think so, but I have to say that there are many advantages of VIPER, which have been listed above. So if it’s a small to medium size project, stick with the MVX architecture. If the MVX architecture still doesn’t hold up with your every class swelling, try VIPER and you may find something new.
In fact, I think that VIPER completely give up Apple’s VC is a bit more than the cost, I still like to use VC to do interface routing, rather than a separate router layer routing, so that both borrowed from the advantages of VIPER, have taken into account the benefits of VC, specific to see the final demo, I will not expand here. A comparison should give you an idea.
5. VIPER reference books
The-Book-of-VIPER
Claims to be the only book on VIPER, but the full version is only available in Russian, but we have Google Translate for everything, as long as it’s not in Martian
6. VIPER code template generator
Since the VIPER architecture is a lot of analogies and has to write a lot of protocols between modules, it would be tiring to write it by hand every time. So try the following code generator to generate VIPER code templates all at once
viper-module-generator
Generamba
ViperCode
Download the Demo
VIPER-DEMO
MVX architecture DEMO
The modified VIERP Demo