MVVM is a software architecture pattern, a variation of Martin Fowler's Presentation Model. It was first proposed by Microsoft architect John Gossman in 2005 and applied in Microsoft's WPF and Silverlight software development. MVVM is derived from MVC and is an evolution of MVC. It promotes the separation of UI code and business logic. Note: This article will combine theory with practice, focusing on an iOS open source project MVVMReactiveCocoa developed using MVVM and RAC, in the hope of helping you practice MVVM. However, before officially starting the article, please think about the following three questions:
With the above questions, let’s get into the main text. Glossary: RAC in this article is the abbreviation of ReactiveCocoa. MVC MVC is the most common architectural pattern used in iOS development and is also the architectural pattern officially recommended by Apple. MVC stands for Model-view-controller, and the relationship between them is as follows: Yes, MVC looks great, model represents data, view represents UI, and controller is responsible for coordinating the relationship between them. However, although technically view and controller are independent of each other, in fact they almost always appear in pairs, a view can only be matched with a controller, and vice versa. In this case, why don't we regard them as a whole: Therefore, M-VC may be a more accurate interpretation of the MVC model in iOS. In a typical MVC application, the controller often becomes bloated due to carrying too much logic, so MVC is often ridiculed as Massive View Controller: iOS architecture, where MVC stands for Massive View Controller. Frankly speaking, some logic does belong to the controller, but some logic should not be placed in the controller. For example, converting NSDate in the model into NSString that can be displayed by the view. In MVVM, we call these logics presentation logic. MVVM Therefore, a good way to solve the Massive View Controller problem is to extract the display logic in the controller and place it in a special place, which is the viewModel. In fact, as long as we put VM between the M-VC in the above figure, we can get the structure diagram of the MVVM mode: From the above figure, we can clearly see the relationship between the four components in MVVM. Note: In addition to view, viewModel and model, there is also a very important implicit component binder in MVVM:
ReactiveCocoa Although, in iOS development, the system does not provide a similar framework that allows us to easily implement the binder function, fortunately, GitHub's open source RAC gives us a very good choice. RAC is a functional responsive programming framework in iOS. It is inspired by Functional Reactive Programming and is a byproduct of Justin Spahr-Summers and Josh Abernathy's development of GitHub for Mac. It provides a series of APIs for combining and transforming value streams. For more information about RAC, you can read my previous article "ReactiveCocoa v2.5 Source Code Analysis Architecture Overview". In the MVVM implementation of iOS, we can use RAC to act as a binder between view and viewModel to elegantly synchronize the two. In addition, we can also use RAC in the model layer, using Signal to represent asynchronous data acquisition operations, such as reading files, accessing databases, and network requests. This means that the latter application scenario of RAC is unrelated to MVVM, that is, we can also use it in the model layer of MVC. summary To sum up, we only need to extract the display logic from the controller in MVC and place it in the viewModel, and then use certain technical means, such as RAC, to synchronize the view and viewModel to complete the transformation from MVC to MVVM. Talk is cheap. Show me the code. Next, let's go directly to the code and look at an example of converting the MVC model to the MVVM model. First, the code of the model layer Person:
Then comes the view layer code PersonViewController. In the viewDidLoad method, we convert the attributes in Person and assign them to the corresponding view for display:
Next, we introduce a viewModel and extract the display logic in PersonViewController into this PersonViewModel:
Finally, PersonViewController will become very lightweight:
How is it? In fact, MVVM is not as difficult as you think, and more importantly, it does not destroy the existing structure of MVC. It just moves some codes, that's all. Well, after saying so much, what are the advantages of MVVM compared to MVC? I think it can be summarized into the following three points:
Through the previous examples, we have already had some understanding of the first point; as for the third point, it may be more obvious for a complex and large application; below, we still use the previous example to intuitively feel the benefit of the second point:
For MVVM, we can regard the view as the visualization of the viewModel, which provides the data and commands required by the view. Therefore, the testability of the viewModel can help us greatly improve the quality of the application. MVVMReactiveCocoa Next, we will move on to the second part of this article, focusing on an open source project MVVMReactiveCocoa developed using MVVM and RAC. Note that this article will mainly introduce the architecture and design ideas of this application, hoping to provide you with a real reference case for practicing MVVM. Some architectures are not necessary for MVVM, but we introduced them to use MVVM more smoothly, especially ViewModel-Based Navigation. Therefore, please make corresponding choices and handle them flexibly in the process of practice based on the actual situation of your own application. Finally, we will take the login interface as an example to explore the practical ideas of MVVM. Note: The following content is based on the v2.1.1 tag of MVVMReactiveCocoa, and some irrelevant codes have been deleted. Class Diagram In order to help us understand the overall structure of MVVMReactiveCocoa from a macro perspective, let's first look at its class diagram: MVVMReactiveCocoa-v2.1.1 From the above figure, we can see that there are two main inheritance systems in MVVMReactiveCocoa:
In addition to providing the base class MRCViewModel/MRCViewController corresponding to the system base class UIViewController, it also provides the base classes MRCTableViewModel/MRCTableViewController and MRCTabBarViewModel/MRCTabBarController corresponding to the system base classes UITableViewController and UITabBarController. Among them, the base class MRCTableViewModel/MRCTableViewController is the most commonly used. Note: The reason why MVVMReactiveCocoa is organized in the form of a base class is that, on the one hand, I am the only main developer and this solution is very easy to implement; on the other hand, the base class method makes it as simple as possible to reuse code and improve development efficiency. Service Bus After the previous discussion, we already know that the main responsibility of the viewModel in MVVM is to obtain the data required by the view from the model layer and convert the data into a form that the view can display. Therefore, in order to facilitate the viewModel layer to call all services in the model layer and uniformly manage the creation of these services, I use the abstract factory pattern to centrally manage all services in the model layer. The structure diagram is as follows: From the above figure, we can see that the service bus class MRCViewModelServices/MRCViewModelServicesImpl mainly includes the following three aspects:
The first two provide services to the viewModel layer in the form of signals, representing asynchronous network requests and other data acquisition operations, while we can obtain the required data in the viewModel layer by subscribing to signals. In addition, the service bus also implements the MRCNavigationProtocol protocol, which is as follows:
Does it look familiar? Yes, the MRCNavigationProtocol protocol is actually defined based on the system's navigation operations and is used to implement ViewModel-Based navigation services. Note that the service bus class MRCViewModelServicesImpl does not actually implement the operations declared in the MRCNavigationProtocol protocol, but only implements some empty operations:
So, how do we implement ViewModel-Based navigation operations? What is the purpose of using MRCViewModelServicesImpl to implement these no-ops? Why do we do this and what is the purpose? Brother, don't worry, please continue to read the next section. ViewModel-Based Navigation Let's first think about a question, that is, why do we need to implement ViewModel-Based navigation operations? Wouldn't it be better to use the system's push/present operations directly in the view layer to complete the navigation? I have summarized the reasons for doing so, mainly in the following three points:
In this case, how do we implement ViewModel-Based navigation operations? We all know that there are two types of navigation operations in iOS, push/pop and present/dismiss. The former is a function unique to UINavigationController, while the latter is a function available to all UIViewControllers. Note that UINavigationController is also a subclass of UIViewController, so it also has the present/dismiss function. Therefore, in essence, no matter what kind of navigation operation we want to implement, it is ultimately inseparable from push/pop and present/dismiss. Currently, MVVMReactiveCocoa maintains a NavigationController stack MRCNavigationControllerStack in the view layer. Regardless of push/pop or present/dismiss, the NavigationController at the top of the stack is used to perform navigation operations, and it is ensured that a NavigationController is presented. Next, let's take a look at the changes in the view hierarchy after MVVMReactiveCocoa performs push/pop or present/dismiss operations. First, let's take a look at the view hierarchy diagram of the application when the user enters the home page after successfully logging in: At this time, the interface displayed by the application is NewsViewController. There is only one element, NavigationController0, in the MRCNavigationControllerStack stack; and NavigationController1 is not in the MRCNavigationControllerStack stack. This is because the view hierarchy is designed to support the sliding switching of TabBarController, which is a special place on the home page. For more information, you can check the GitHub open source library WXTabBarController. Here, we don't need to care too much about this issue, we just need to understand the principle. Next, when the user clicks a cell in the NewsViewController interface and enters the warehouse details interface through push, the view hierarchy diagram of the application is as follows: The application pushes the warehouse details interface to its own stack through the NavigationController0 element at the top of the MRCNavigationControllerStack. At this time, the interface displayed by the application is the pushed warehouse details interface RepoDetailViewController. Finally, when the user clicks the switch branch button in the lower left corner of the warehouse details interface and the branch selection interface pops up through the present method, the view hierarchy diagram of the application is as follows: The application uses the top element NavigationController0 of the MRCNavigationControllerStack to present NavigationController5. At this time, the application displays the root view SelectBranchOrTagViewController of NavigationController5. Note that since pop and dismiss are the inverse operations of push and present, you only need to read the view hierarchy diagram from bottom to top, and will not repeat them here. Wait, if I remember correctly, the MRCNavigationControllerStack stack is in the view layer, and the service bus class MRCViewModelServicesImpl is in the viewModel layer. As far as I know, the viewModel layer cannot import anything from the view layer, more strictly speaking, it cannot import anything from UIKit, otherwise it will violate the basic principles of MVVM and lose the testability of the viewModel. Under this premise, how do you make the two related? Yes, this is the purpose of implementing those empty operations in MRCViewModelServicesImpl. The viewModel calls the empty operations in MRCViewModelServicesImpl to indicate that the corresponding navigation operations need to be performed, and MRCNavigationControllerStack captures these empty operations through Hook, and then uses the NavigationController at the top of the stack to perform the actual navigation operations:
By using Hook, we finally realized the ViewModel-Based navigation operation, and did not introduce anything from the view layer into the viewModel layer, thus achieving decoupling. Router Another point worth mentioning is that when we call the navigation operation in the viewModel, we only pass in the instance of the viewModel as a parameter. So when we perform the actual navigation operation in the MRCNavigationControllerStack, how can we know which interface to jump to? To this end, we configure a mapping from viewModel to view and agree on a unified method to initialize the view: initWithViewModel:
Login screen Finally, let's take a look at some key codes of viewModel and view in the login interface and discuss the specific practice of MVVM. Note that we will avoid specific business logic as much as possible and focus on the practice of MVVM. The following is a screenshot of the login interface: The main interface elements are:
Analysis: According to our previous discussion on MVVM, viewModel needs to provide the data and commands required by view. Therefore, the content of MRCLoginViewModel.h header file is as follows:
It is very intuitive. What needs to be explained in particular is that the validLoginSignal property represents whether the login button is available. It will be bound to the enabled property of the login button in the view. Next, let's take a look at some key codes in the implementation file of MRCLoginViewModel.m:
Next, let's take a look at some key codes in MRCLoginViewController:
@end
In summary, after we extract the display logic in MRCLoginViewController into MRCLoginViewModel, the code in MRCLoginViewController becomes more concise and clear. The key point of practicing MVVM is that we must be able to clearly analyze the data and commands that the viewModel needs to expose to the view, and these data and commands can represent the current state of the view. Summarize First, we introduced the concepts of MVC and MVVM and the evolution from MVC to MVVM from a theoretical perspective; then, we introduced two usage scenarios of RAC in MVVM; finally, from a practical perspective, we focused on an open source project MVVMReactiveCocoa developed using MVVM and RAC. In general, I think MVVM in iOS can be divided into the following three different levels of practice, which correspond to different applicable scenarios:
In conclusion, I hope this article can dispel your concerns about the MVVM model and take action now. Reference Links
|
<<: iOS: Let's talk about Designated Initializer
>>: How to write a good bug report
Yan Jie's 14-day ultimate waist and abdomen s...
Ailsa Harvey Walking fish The gills of the Mexica...
[[152403]] Raghav Mathur is the chief marketing o...
Today I will talk about how to promote Xiaohongsh...
Human beings are small but tenaciously multiplyin...
A piece of news about Windows 10 these days has ma...
In the past few days, I have looked at no less th...
This issue will combine the novel marketing model...
Recently, the question of "where has the new...
★ It is not new that the temperature rises and pr...
In the past two years, online self-made dramas ha...
1Cold wave orange warning! The Central Meteorolog...
Whether you are doing user operations, new media ...
Luo Yonghao was slapped in the face again, and th...
The Pingliangtai Site in Huaiyang was selected as...