There have been articles touting MVVM application development frameworks all the time, articles overstating MVVM and criticizing classic MVC frameworks including those used by iOS and Android. This article is intended to pour cold water on those who hold smelly feet, although it may attract criticism, but the purpose is to give some reference and advice to those who are just starting out, so as not to go wrong. At the same time, it also gives those who can not extricate themselves from the partners of the alarm, so as not to go further and further on the wrong road.

—— MVVM is not a framework, but a simple folder classification ——

How MVVM was introduced

It was around 2010 that mobile terminal development became popular. At first, iOS, Android and WinPhone competed with each other on three major platforms. Later, the latter withdrew from the competition and became a duopoly. From the perspective of application architecture and framework system provided for developers, both platforms have introduced the development mode of classic MVC three-layer structure, which represents the meaning of model, view and control. The original intention of the framework is simple: the view is responsible for displaying and rendering, the model is responsible for implementing the business logic, and the control is responsible for scheduling the events of the view and calling the business logic and notifying the view of the refresh notification. The three parts are loosely coupled, each doing its job. Here is the classic MVC framework structure:

It is a pity that both Android and iOS have standard definitions and implementations of C and V: Android view part of the implementation is to define a variety of controls and through XML files to assemble the view layout interface, iOS view implementation is also to define a variety of controls and through XIB or SB to assemble the view layout interface; The control part of Android is through Activity, and the control part of iOS is through UIViewController. What about the model part? Because the business logic and application scenarios of each application are different, the two platforms cannot and cannot define a common model layer, leaving the definition of the model layer to the developers to implement. However, this creates a potential danger for our developers when developing applications using the MVC framework.

Early application development is relatively simple, because there is no standard definition of model layer, and at the time of the project to generate control layer and left a lot of places available for developers to write code, so a lot of developers are understandably request, database operations, business logic, network packet assembled and parsing, and so on in the control layer inside all the code, There is no need for a model layer definition. As time goes by and the complexity of the application increases, the C layer expands. There may be thousands of lines of code in a single controller. So someone started looking for a solution to slim down the C layer. It is also a pity that no one has yet thought of abstracting the M layer, but has solved the problem in the following way:

  • Can the data packets exchanged between the client and server define data objects with attributes but no methods? You don’t need to interact with raw XML or JSON or other formatted messages to process and render the interface, just manipulate the data object. The solution is to define data models based on the messages exchanged between the client and server, and then develop a set of parsers that interact with XML or JSON data models. Finally, the object data models with only data and no methods are unified in one place, and then they are defined as M model layer *(hoo! Finally, the model layer is defined but: Are you kidding me??) . No more XML or JSON parsing and code to read messages directly on the C layer! Instead, I moved the code to the model layer (see, I’m finally using the MVC framework!). *. All right! The first step to losing weight is success. But, the problem is still there, my business logic is still in C layer, it seems that MVC framework is only so much. It doesn’t solve my problem at all. No, I can’t use MVC anymore to develop my applications. I’m going to find another way. I’m going to continue to slim down the C layer.

  • An interface of mine is bound to a certain business logic. The display of this interface is realized by calling a certain business logic. After the business logic is completed, the interface should be updated directly. This tight call and update relationship does not require c-level intervention at all. Therefore, you can bind the refresh of this interface to the invocation of business logic, combining the two into a closed and separate whole and forming a separate class. This removes the code from the class and places it in a separate folder. What should I call this part? Call it the View model layer VM. Classes in the View-model layer define a unique interface for external use to be invoked by the C layer. So I finally took a big chunk of the code out of the C layer. I have successfully implemented a further slimming of the C layer and abstracted a viewmodel layer! The viewmodel layer is designed for interaction and coupling between the view, model, and viewmodel layer. Let me see if I can continue to slim down the C layer.

  • A lot of my view events are handled in the C layer, so can I take the C layer event processing out as well? Just take it out. But how to get it out? So I kept looking, finally found a thing called RAC, which is good, it can handle the various events of the view, and can handle the continuous network calls. Wait… RAC is just a bit opaque! Hard to learn, code hard to read and debug. How to do? It doesn’t matter, as long as it can slim down the C layer of code… The biggest thing is to run a little more pit, more training. Well! So let me put this part of the code into the VM layer as well.

    . Shout!!!!!! Layer C finally slimmed down. And lo and behold, there really is no code left in my C layer… It no longer handles view events because they were handled by RAC, it no longer handles view refreshes and business logic calls because the viewmodel MV was handled, and it no longer handles data parsing because the model layer was replaced. Well… I’m going to give a name to this framework that doesn’t have or need a C layer. What should I call it? Let’s call it MVVM… My app can get rid of layer C, and THEN I’ll run. Expose the uselessness of layer C.

Is it really so? The answer is NO!!

The first thing I want to say about a good framework is that the hierarchy is not a simple grouping and partitioning of code. The hierarchy is horizontal and the modules are vertical. This involves the coupling between layers and the division of responsibilities, as well as the definition and way of interactive interfaces between layers. At the same time, the design within layers should also have a high degree of cohesion and structure. These design requirements are not reflected in the so-called MVVM.

MVVM is said to be a bidirectional binding technology derived from Microsoft’s data view. That is, there is a VM class to implement data changes update view, view changes update data processing, the whole process does not need to be separately coded to handle. This technology is similar to the DDX/DDV technology in the early MFC. MVVM is more of a variation on data binding technology than a framework. The elements of the layers in the framework have attributes of responsibility and function. As defined in MVVM, M can only be understood as pure data. The concept of a batch of data structures forming a layer is missing from all the system framework libraries in iOS and Android. Even the so-called storage tier is a combination of databases, tables and database engines. And the reason why the controller bloat is actually because our handwritten layout view is done in the controller which takes up a lot of code, and the business processing and implementation is done in the controller. Apple and Google have provided views built using SB and XML. Complex business logic can be broken up into multiple sub-view controllers or fragments. If a design of good enough C layer, how to expand so said!

  • First of all, what is M in MVC? Is it a data model? The answer is NO. His correct definition is the 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 also clearly described in the classic MVC diagram above: The control is responsible for calling the model, and the model notifies the control of the processing result, which notifies the view to refresh. Therefore, we cannot simply understand M as a dry data model with only attributes but no methods. In fact, there is a basic design principle involved in this, that is the basic object-oriented design principle: what is a class? A class should be an abstraction of objects with the same operations and different properties. I don’t think there has ever been a collection of data models in any system that has only data but no methods defined as a single and abstract model layer for everyone to use. We can’t treat a data model folder as a layer, it doesn’t follow the rules of horizontal segmentation. So the so-called definition of M layer in MVVM is a pseudo concept.

  • I have shown above that the M layer is the business model layer, not the data model layer, and that the business model layer should encapsulate all the implementation of the business logic and be view-independent. We should not tie the presentation logic of a view to the business process logic, because there may be a business logic with different presentation forms, or the interface presentation may change as the application upgrades, but the business logic is relatively stable. Even if a view is tightly coupled to the business, it should not be strongly coupled. Therefore, the above so-called VM is a very poor way to bind the presentation of the view and the processing logic of the business together, because it has completely deviated from the basic principle that the presentation and implementation of the system should be separated. And this kind of design thinking is contrary to the idea of layering. Because it presents the problem of tight coupling and bidirectional dependence between view and business, as well as the existence of tight coupling with the so-called M layer. So the definition of the so-called VM layer in MVVM is also a pseudo concept. The so-called VM layer is just a functional split by page, there is no such concept as a layer.

  • Let’s talk about event handling. The classic C layer design is responsible for event handling and scheduling, whether button clicks, UITableview delegate and ListView Adapter are best handled in THE C layer, which is consistent with the most essential definition of the C layer: Layer C is a module responsible for scheduling and control, which is the glue between layer V and Layer M. Its function is to process the events of the view, then call the business logic, receive the notification of the processing result of the business logic, and then inform the view to refresh the interface. This is the significance of the existence of layer C. And the system is designed that way by default. The emergence of RAC removes this part of the processing to live substitution. This is achieved through what RAC calls reactive and trigger mechanisms where event scheduling can be done anywhere at any time. The purpose of this is to allow us to split and decompose the code. But what about the resulting problems? That is, the scheduling processing logic and function construction of the same unit are completely placed in one place, but different unit logic is scattered in different places, which cannot be classified and unified management and maintenance. So you can’t immediately know exactly how and where all the scheduling is implemented for a feature. Because RAC completely glue function building and event handling into a large function body, code within code pattern, this approach seriously undermines the object-oriented design pattern theory of the separation of build and processing. More troubling are the high cost of learning and maintenance, the difficulty of reading and understanding code, and the ubiquitous use of closures. Is this a nightmare for a beginner? Is it a nightmare for maintenance and code debugging once something goes wrong? And there are serious problems with circular references when used improperly. As a result, when the responsibilities of a scheduling manager at layer C are taken over by RAC, these processes become scattered and unorganized, making it impossible to do unified management of things like hooks and AOP. There is no denying that RAC has some advantages in handling sequential calls and sequential responses. One example is that we may have multiple network requests to the server in a row, where this processing in RAC is convenient to solve the problem. But what I want to say is that when such a scenario exists, we should digest the continuous network calls inside layer M, and only provide layer C with a simple and convenient interface, so that layer C does not need to care about the continuity of such calls. Therefore, the introduction of RAC in order to digest the C layer code does not simplify the system but reduces its maintainability and readability. The RAC mechanism is simply not suitable for event handling. Excellent application and framework is not in the number of code, but the overall system code is simple and easy to read, each part of the responsibility is clear, easy to maintain debugging

The root cause of the introduction of —— MVVM was the misperception of the M layer caused by ——

MVC M layer implementation criteria

Having said so much, it can be concluded that the so-called MVVM is not a so-called framework or mode, it is just a pseudo framework, it is only the function and processing according to the way of folders are divided, the final result is a chaotic system. There is no hierarchy at all. The only advantage is that the CODE and functionality of the C layer are completely weakened. In fact, the most fundamental reason for this design method is that the M layer is not properly understood, defined and split. So how should we define and design the M layer correctly? Here are a few of my own (and perhaps not everyone else’s) rules:

  • The code defined in the M layer should be completely independent of the V and C layers, that is, the objects in the M layer should exist independently of any objects in the C and V layers. The optimal design structure of the whole framework is that V layer exists independently of C layer, M layer does not exist independently of C layer and V layer. C layer is responsible for the association of the two layers, V layer is only responsible for display, M layer holds the specific implementation of data and business, while C layer deals with event response, business invocation and notification interface update. The three must be clearly defined as one-way dependencies, not two-way dependencies. Here is the dependency diagram for the three layers:

It is only when the different parts of your system design are unidirectional dependencies that it is possible to easily split the layers and replace the functionality of each layer independently.

  • M layer to complete the encapsulation of business logic implementation, general business logic is mostly involved in the business interaction between the client and the server. The M layer encapsulates all business details such as the network protocols used (HTTP, TCP, etc.), the data formats used to interact with the server (XML, JSON, etc.), local caching and database storage (COREDATA, SQLITE, etc.), and these are not exposed to the C layer. All calls to the C layer are implemented by member methods provided by each business class in the M layer. That is, 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 advantage of this is that the communication protocol, data format, and local storage changes between the client and server do not affect the overall framework of any application, because the interface provided to layer C is unchanged, and only the code of layer M needs to be updated and updated. For example, if we want to change the network request library from ASI to AFN, we only need to change the M layer. The entire C layer and V layer code remain the same. Below is the definition diagram of the internal layers of the M layer:

  • Since our application is a whole and divided into modules, the internal structure of the business layer should also be divided into functional modules, rather than simple and flat plane encapsulation of the business layer according to the interface for communication with the server. I’m sure there are a lot of people who encapsulate the M layer by simply encapsulating the interface with the server. The following two different M layers implement business encapsulation:

We can further abstract the interface and implementation of the M layer from the business logic. One of the advantages is that the same interface can be implemented in different ways, and the M layer can hide a lot of internal data and methods without exposing them to the caller. Through the separation of interface and implementation, we can not change the original implementation, on the basis of to reconstruct the business part of the implementation, at the same time, the model is easy to MOCK a test implementation, which can be easily when debug switch between real and MOCK, without having to interact and server-side debugging every time, So as to realize the development and debugging between client and server respectively. Here is an upgraded version of the M-tier architecture:

  • The problem of how layer M interacts with layer C also needs to be considered, because layer M does not need to know the existence of layer C and layer V, so how can Layer M notify Layer C after business processing is completed? There are many ways to do this:
    • We can define Delegate protocols for the m-layer’s notification logic, then let the C layer implement those protocols, and then the M-layer provides a Delegate attribute to assign objects to handle business notifications.
    • We can also define a number of NSNotifications or event buses, and then send notifications when the m-layer business is finished, and implement the notification processing logic in the C layer.
    • Notification of business logic completion can be implemented in the form of closure callbacks or interface anonymous implementation objects. And you can define standards: The last argument to all m-layer objects’ methods is a standard block or interface callback of the following format:
typedef void (^UICallback)(id obj, NSError * error);
Copy the code

This pattern actually applies to many systems. You can take a look at the definition of the CLGeocoder class in Apple’s CoreLocation.framework. Another point is that in AFN and ASI, the network request part is divided into two block callbacks for success and failure. However, it is recommended that asynchronous notification callbacks to THE C layer are not divided into two blocks, but each block is handled with two parameters. Because there may be parts of the code that are similar in both success and failure, if separated, the problem of duplicate code will occur.

MVC M layer implementation of a simple example

Finally we use a simple user system login system to achieve an M layer.

1. Define the standard M layer asynchronous callback interface:

// Define the standard C layer callback block. The OBJ in this case will vary depending on the method returned by the object. typedef void (^UICallback)(id obj, NSError * error); Typedef id (^DataParse)(id retData, NSError * error);Copy the code

2. Define base classes for all M layer business classes, so that we can do a lot of processing in the common base class. Such as network layer unified call, encryption and decryption, compression and decompression, we can also do AOP and HOOK aspects of processing.

@interface ModelBase // Define a method to stop the request -(void) stopRequest; /** * defines a unique entry method for a network request * URL request URL *inCallback: C layer notification block */ -(void) startRequest:(NSString*) URLinParam:(id)inParam outParse:(DataParse)outParse  callback:(UICallback)callback;
     @end
Copy the code

Define a user class:

    @interface  ModelUser:ModelBase
  
        @property(readonly) BOOL isLogin;
        @property(readonly) NSString *name; // Define the login method. Note that the implementation of this login method may make N consecutive network requests internally, but we require that they be handled within the login method and not exposed to Layer C. -(void)login:(NSString*)name password:(NSString*)password callback:(UICallback)callback; // Define the login method -(void)logout:(UICallback)callback;
    @end

  

Copy the code

4. Define an M-layer global system class (optional) that can be a singleton:

@interface ModelSystem:ModelBase +(ModelSystem*)sharedInstance; // Aggregate the user objectreadonly, that is, layer C cannot directly modify the user object, so as to ensure security, but also shows that layer C on the use of user object permissions. @property(readonly) ModelUser *user; // Define the module @end for other aggregationsCopy the code

5. Invoke user login at layer C:

  @implementation LoginViewController

    -(IBAction)handleLogin:(UIButton*)sender
   {
        sender.userInteractionEnabled = NO;
        __weak LoginViewController  *weakSelf = self;
       [[ModelSystem sharedInstance].user  login:@"aaa" password:@"bbb"  callback:^(ModelUser *user, NSError *error){

        if (weakSelf == nil)
               return;
       sender.userInteractionEnabled = YES;
       if(error == nil) {// Successful login, page jump}else{// Displays error information. }}]; } @endCopy the code

As you can see, the upper part of the C layer is very straightforward and the code is easy to read and understand. At the same time, we also see that the C layer does not need to know how the login implementation of the M layer requests the network, as well as several network operations requested, and what protocol is used, as well as what data message format, all these are encapsulated in the M layer internal implementation. All the C layer has to do is simply call the methods provided by the M layer and notify the interface in the callback to update. The entire C-level logic can be done in just a few dozen lines.


Welcome to follow my github address, follow Ouyang Big Brother 2013