IOS architecture-reveal the secrets of MVC, MVP, MVVM, and VIPER

Source: Internet
Author: User

IOS architecture-reveal the secrets of MVC, MVP, MVVM, and VIPER

IOS Architecture

Demystifying MVC, MVP, MVVM, and VIPER

Is it strange to use MVC in IOS? Do you have any concerns about switching to MVVM? I have heard of VIPER, but I'm not sure whether it's worth trying?

Continue reading and you will find the answer to the above question. If you do not have the answer you want, you can comment on me.

You are about to start learning about the ios architecture model. We will briefly review some of the currently popular architecture models, compare them in terms of principles, and then make some small examples to practice. If you need more details, I also sorted out some links for you.

Learning design patterns may be addictive, so be careful: reading this article may bring you more problems, such

  • Who should make a network request: model or controller?
  • How to pass a model to a new view?
  • Who created a new VIPER module: Router or Presenter?

     

    Why care about the architecture?

    If you do not do this, you will one day be faced with debugging dozens of huge classes, and you will find that you cannot find and fix any bugs for the moment. It is also difficult for you to have a general impression on these classes, so you will never be able to repair or improve some important details. If your program already works like this, it is very likely that:

  • This class is a subclass of UIViewController.
  • Your data is directly stored in UIViewController.
  • You have almost no work on UIViews.
  • Your model is a simple data structure.
  • No unit test

    Even if you follow Apple's guidance and use Apple's MVC, this problem may occur. Don't be discouraged. Apple's MVC also has some disadvantages. Let's discuss this issue later.

    Let's define which features should a good architecture meet:

  • Strictly balanced distribution of tasks between role entities;
  • Testability (usually from the previous rule, which can be easily implemented in a good architecture );
  • Ease of use and low maintenance costs;

    Why distribution?

    When we need to figure out how one thing works, our brain needs to load the complexity of this thing. Of course, the more you grow, the more your brain can adapt to and understand more complex things. However, this kind of capability is not constantly linear, and you will soon encounter a bottleneck. Therefore, the simplest way to defeat complexity is to assign each instance a single responsibility principle ).

    Why is it testable?

    No one will doubt the benefits of unit testing when restructuring and adding new features. It also allows developers to discover many runtime problems. If these problems are found on the user's device, it takes at least one week to fix and reach the user (takes a week ).

    Why ease of use

    This does not seem to require an answer. It is worth mentioning that the best code is no code. Therefore, less code also indicates fewer errors. Writing less code does not mean that developers are more lazy. You should not always be inclined to use a more perfect solution, rather than the maintenance cost.

    MV (X) highlights

    When it comes to architecture models, we have many options:

  • MVC
  • MVP
  • MVVM
  • VIPER

    Assume that an application is divided into three parts:

  • Models (model)-data access layer for data or operation data, such as the 'person 'or 'persondataprovider' class.
  • Views-the presentation layer (GUI). iOS generally starts with 'ui '.
  • Controller/Presenter/ViewModel? (View Model)-the intermediary between the Model and View is generally responsible for responding to changes to the Model, accepting user operations, modifying the Model, and updating the View.

    Entity division enables us:

  • Better understanding (because we already know what the module is doing)
  • Reuse (mainly applicable to View and Model)
  • Independent Test

    Let's start with the mv (x) mode and then talk about VIPER.

    MVC

    How to use it

    Before discussing the Apple version of MVC, let's take a look at the traditional MVC (traditional one)

    In this case, the View is stateless. Once the Model changes, it is simply presented through the Controller. Think about Web pages. Once you press a link, the browser reloads the new page. Although traditional MVC can be implemented in ios, it does not mean much-three entities are tightly coupled and each entity is visible to other entities. This greatly reduces the reusability of modules-that is why your applications do not use MVC. For this reason, we will not write the MVC example.

    Traditional MVC does not seem suitable for IOS development.

    Apple's MVC

    Expected

     

     

     

    The Controller is the intermediary between views and models so that they are invisible to each other. We usually construct some minimum reusable controllers, but some tricky business logic that is not suitable for putting in the Model will also be put in it.

    Theoretically, appleMVC looks very simple. But do you always feel wrong? You must have heard that someone has constructed an extremely large Controller. In addition, the uninstallation of View Controller has become an important issue for ios developers. Why is this happening? Why should Apple improve the traditional MVC? (View controller offloading)

    Apple's MVC

    Reality

    CocoaMVC encourages you to write ultra-large View Controllers, because it is closely related to the life cycle of views. It is hard to say that they are independent of each other. Although you can share part of the business logic and data conversion work to the Model, you do not have many choices when it comes to release work. Most of the time, the only responsibility of a view is to send an action to the Controller, which eventually becomes a proxy and is responsible for all the tasks you can think of as data and sending and receiving network requests.

    How many times have you seen this Code:

    var userCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCelluserCell.configureWithUser(user)

    This cell is actually a view and is directly configured using the model. This violates the MVC rules, but when you use it like this, you do not think there is anything wrong. If you strictly abide by MVC, you should configure the view from the View Controller, instead of passing a model to the view, but this will make your View Controller larger.

    In CocoaMVC, it is reasonable to create a large view controller.

    This problem may not be obvious until it involves Unit Testing (If your project contains Unit Testing )). Because the view is closely integrated with the Controller, you must use some very creative methods to test the life cycle of the view. When constructing a View Controller in this way, your business logic should be separated from the view layout as much as possible.

    Let's look at a simple example:

    #import UIKitstruct Person { // Model    let firstName: String    let lastName: String}class GreetingViewController : UIViewController { // View + Controller    var person: Person!    let showGreetingButton = UIButton()    let greetingLabel = UILabel()        override func viewDidLoad() {        super.viewDidLoad()        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)    }        func didTapButton(button: UIButton) {        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName        self.greetingLabel.text = greeting            }    // layout code goes here}// Assembling of MVClet model = Person(firstName: "David", lastName: "Blaine")let view = GreetingViewController()view.person = model;

     

    MVC assembly (coding of MVC in the Code) can be executed when the view controller is presented.

     

    However, we cannot directly call the methods related to UIView (viewDidLoad, didTapButton ), this may cause failure to test the loading and presentation layer logic of the GreetingViewController view (although there is not much such logic in the above example). This is not a good unit test.

     

    In fact, loading and testing UIView on a particular simulator (such as an iPhone 4S) does not ensure that it works well on other devices (such as ipad. Therefore, we recommend that you delete "Host Application (Host Application)" from your unit test configuration and test it without the Host Application.

     

    The interaction between views and controllers is not testable in Unit Tests (aren't really testable with Unit Tests ).

     

    In this case, Cocoa MVC seems to be a bad design pattern. However, we use the features at the beginning of the article to evaluate it:

  • Distribution-views and models are separated, but views are closely coupled with controllers.
  • Testable-due to poor allocation, you can only test your model.
  • Ease of use-Code involves few patterns and everyone is familiar with this pattern, so it is easy to maintain it even for inexperienced developers.

     

    If you do not want to invest more time in the architecture, Cocoa MVC is your preferred mode. If you think that the maintenance cost of this mode is too high, it may be that your project is too designed.

     

    In terms of development speed, Cocoa MVC is the best design mode.

     

     

     

    MVP

    Cocoa MVC commitment delivery

     

    Does it look like Apple's MVC? This is indeed the case, and its name is MVP (a variant of Passive View). Does this mean that Apple's MVC is actually an MVP? No, it's not. If you remember, in Apple's MVC, views and controllers are tightly coupled, while in MVP, controllers are mediators and moderators, the life cycle of the View Controller is independent of the view and can easily imitate a view. Therefore, the Presenter can update data and view status without any layout code.

    If I tell you, the view controller is the view.

    In MVP, The subclass of UIViewController is actually a view rather than a Presenter. This distinction provides better testability, but reduces the development speed because you must manually bind events and data, you can look at the following example:

    #import UIKitstruct Person { // Model    let firstName: String    let lastName: String}protocol GreetingView: class {    func setGreeting(greeting: String)}protocol GreetingViewPresenter {    init(view: GreetingView, person: Person)    func showGreeting()}//Presenterclass GreetingPresenter : GreetingViewPresenter {    unowned let view: GreetingView    let person: Person    required init(view: GreetingView, person: Person) {        self.view = view        self.person = person    }    func showGreeting() {        let greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName        self.view.setGreeting(greeting)    }}//viewclass GreetingViewController : UIViewController, GreetingView {    var presenter: GreetingViewPresenter!    let showGreetingButton = UIButton()    let greetingLabel = UILabel()        override func viewDidLoad() {        super.viewDidLoad()        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)    }        func didTapButton(button: UIButton) {        self.presenter.showGreeting()    }        func setGreeting(greeting: String) {        self.greetingLabel.text = greeting    }        // layout code goes here}// Assembling of MVPlet model = Person(firstName: "David", lastName: "Blaine")let view = GreetingViewController()let presenter = GreetingPresenter(view: view, person: model)view.presenter = presenter

    Important Notes on assembly:

    The MVP mode is composed of three independent layers. We do not want the view layer to understand the model. It is incorrect to present the model in the View Controller (here it is the view. Therefore, we must do this job elsewhere. In addition, we can use the application-level routing service to redirect the view-to-view. These problems exist not only in MVP, but also in all models.

    Let's look at the features of MVP:

  • Distribution-we divide the Presenter and model, as well as a very simple view (the view does not operate on the model ).
  • Testable-Excellent, we can test most of the business logic, because the view is only responsible for display.
  • Ease of use-in our very simple example, the code volume almost doubles compared to MVC. However, the MVP thinking is clear.

    In iOS, MVPs have superb testability and a large amount of code.

    MVP

    Binding and alerting

    This is a variant of MVP -? The Supervising Controller MVP. This variant includes a directly integrated view and model, while The Supervising Controller still processes operations from the view and can change The view.

    Supervising Presenter variant of the MVP

    However, as we have already said, fuzzy separation of duties, Close coupling between views and models is not good. Working principle is similar to Cocoa desktop development.

    Compared with traditional VMC, I do not see the advantages of this architecture.

    MVVM

    The latest and largest music video (x)

    MVVM is the latest MV (x), so we hope it solves the problems faced by MV (x.

    Theoretically, Model-View-ViewModel looks very good. Both views and models are familiar to us. The mediation is called ViewModel.

     

    It is similar to MVP:

  • In MVVM, the view controller is used as the view.
  • There is also no close coupling between views and models.

    In addition, it is a bit like the Monitoring Version of the MVP (Supervising Controller MVP), but this time not to bind the View and Model, but to complete this function in the View Model.

    So how does the View Model work in ios? It is basically irrelevant to your view and UIKit. The View Model updates itself while calling the update Model. Because we have a View bound to the View Model, the View is also updated accordingly.

    Bind

    Here we will discuss this issue. The binding tool is provided on OSX, but not on IOS. Of course, we have KVO and notifications, but they are not as convenient as binding.

    If we do not want to implement this function by ourselves, we have at least two options:

  • A kvo-based Data Binding database RZDataBinding or SwiftBond
  • A list of related class libraries: functional reactive programming, such as ReactiveCocoa, RxSwift, PromiseKit.

    In fact, if you hear "MVVM", you can think of it as ReactiveCocoa, and vice versa. Although you can use simple binding to implement MVVM, most MVVM modes directly use ReactiveCocoa (or its branches ).

    There is a bitter truth about the responsive framework (ReactiveCocoa): The larger the right, the greater the responsibility. Using a responsive framework can easily mess up things. In other words, if you do something wrong somewhere, you may have to spend a lot of time debugging the program. You just need to look at the stack call below to see how much trouble it is:

    Reactive Debugging

    In our simple example, using the FRF framework or KVO seems too heavy. Instead, we explicitly require the View Model to use showGreeting and the simple callback function greetingDidChange to update the View.

    #import UIKitstruct Person { // Model    let firstName: String    let lastName: String}protocol GreetingViewModelProtocol: class {    var greeting: String? { get }    var greetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change    init(person: Person)    func showGreeting()}class GreetingViewModel : GreetingViewModelProtocol {    let person: Person    var greeting: String? {        didSet {            self.greetingDidChange?(self)        }    }    var greetingDidChange: ((GreetingViewModelProtocol) -> ())?    required init(person: Person) {        self.person = person    }    func showGreeting() {        self.greeting = "Hello" + " " + self.person.firstName + " " + self.person.lastName    }}class GreetingViewController : UIViewController {    var viewModel: GreetingViewModelProtocol! {        didSet {            self.viewModel.greetingDidChange = { [unowned self] viewModel in                self.greetingLabel.text = viewModel.greeting            }        }    }    let showGreetingButton = UIButton()    let greetingLabel = UILabel()        override func viewDidLoad() {        super.viewDidLoad()        self.showGreetingButton.addTarget(self.viewModel, action: "showGreeting", forControlEvents: .TouchUpInside)    }    // layout code goes here}// Assembling of MVVMlet model = Person(firstName: "David", lastName: "Blaine")let viewModel = GreetingViewModel(person: model)let view = GreetingViewController()view.viewModel = viewModel

    Review:

  • Distribution-our small example seems unclear, but in fact the MVVM view has more responsibilities than the MVP view. First, you need to bind all status updates with the View Model. Second, all events are forwarded by the Presenter and not updated by yourself.
  • Testable-View Model does not know anything about the View, which makes it easy for us to test it. A view can also be tested, but it depends on UIKit. You may want to skip it.
  • Ease of use-in our example, it seems that there are as many code as MVP, but in real programs, you should not manually bind events or update views. If you use binding, there will be a very small amount of MVVM code.

    MVVM is very attractive because it combines the advantages of the above method. In addition, it does not require additional code to update the view because the update of the view is bound to the view. At the same time, the test is also good.

    VIPER

    Transfer Lego's experience to iOS apps

    VIPER is our final alternative because it is not extended from MV (X.

    Now, you must agree that the detailed division of responsibility is very good. VIPER iterates responsibility again. Here, we have five levels.

     

  • Interactor-includes data entities, business logic, and network requests. For example, you can create a new instance or obtain an instance from the server. Some services or management used for this purpose are not considered as part of VIPER, but as some external dependencies.
  • Presenter-The Interactor method is called for business logic that includes UI-related (but does not include UIKit-related.
  • Entities-your data object, not the data access layer, because it is the responsibility of Interactor.
  • Router-connects VIPER modules.

    Basically, the content displayed on a screen or a scenario composed of multiple related screens can be a VIPER.

    What do you want to do with Lego? Whatever you want.

    If we use MV (X) for comparison, we can see the differences in Responsibility Division:

  • The logic of Model (Data Interaction) is transferred to Interactor, and Entities are only simple data structures.
  • Only the UI display responsibilities of Controller/Presenter/ViewModel are moved to the Presenter, but there is no data modification capability.
  • VIPER has a specific Router module responsible for navigation.

    Routing in an appropriate way is a challenge for IOS apps. This problem is not solved in the MV (X) mode.

    This example does not include routing interactions between modules. This problem is not covered because music videos (X) are used as the topic.

    #import UIKitstruct Person { // Entity (usually more complex e.g. NSManagedObject)    let firstName: String    let lastName: String}struct GreetingData { // Transport data structure (not Entity)    let greeting: String    let subject: String}protocol GreetingProvider {    func provideGreetingData()}protocol GreetingOutput: class {    func receiveGreetingData(greetingData: GreetingData)}class GreetingInteractor : GreetingProvider {    weak var output: GreetingOutput!        func provideGreetingData() {        let person = Person(firstName: "David", lastName: "Blaine") // usually comes from data access layer        let subject = person.firstName + " " + person.lastName        let greeting = GreetingData(greeting: "Hello", subject: subject)        self.output.receiveGreetingData(greeting)    }}protocol GreetingViewEventHandler {    func didTapShowGreetingButton()}protocol GreetingView: class {    func setGreeting(greeting: String)}class GreetingPresenter : GreetingOutput, GreetingViewEventHandler {    weak var view: GreetingView!    var greetingProvider: GreetingProvider!        func didTapShowGreetingButton() {        self.greetingProvider.provideGreetingData()    }        func receiveGreetingData(greetingData: GreetingData) {        let greeting = greetingData.greeting + " " + greetingData.subject        self.view.setGreeting(greeting)    }}class GreetingViewController : UIViewController, GreetingView {    var eventHandler: GreetingViewEventHandler!    let showGreetingButton = UIButton()    let greetingLabel = UILabel()        override func viewDidLoad() {        super.viewDidLoad()        self.showGreetingButton.addTarget(self, action: "didTapButton:", forControlEvents: .TouchUpInside)    }        func didTapButton(button: UIButton) {        self.eventHandler.didTapShowGreetingButton()    }        func setGreeting(greeting: String) {        self.greetingLabel.text = greeting    }        // layout code goes here}// Assembling of VIPER module, without Routerlet view = GreetingViewController()let presenter = GreetingPresenter()let interactor = GreetingInteractor()view.eventHandler = presenterpresenter.view = viewpresenter.greetingProvider = interactorinteractor.output = presenter

    Another advantage:

  • Distribution-VIPER is undoubtedly the champion of functional subdivision.
  • Testability-there is no surprise here. A better distribution is equivalent to a better testability.
  • Ease of use-you can guess from the two features above that you must write a lot of interfaces and many very small classes.

    About lego

    When you start to use VIPER, you may be able to use Lego blocks to build the Empire State Building. smoothness is a signal of design problems. Maybe you have adopted VIPER too early (the granularity is too small). You should consider some simpler things. Some people ignore this and continue to use cannons to fight mosquitoes. I think they think that using VIPER in the current phase will benefit in the future, even if the current maintenance cost is a bit high, it is acceptable. If you agree with this idea, I suggest you try Generamba? -Tools used to generate the VIPER framework. I personally think this is like replacing a slingshot with an automatic aiming system gun.

    Conclusion

    We have learned about these architecture models. I hope you can find some answers to these questions. However, I believe that you are aware that there is no omnipotent architecture. Therefore, choosing an architecture model is a matter of weighing weights under special circumstances.

    Therefore, it is natural to mix different architecture models in the same application. For example, you have started to use MVC, And then you realize that it is too difficult to implement and maintain a specific screen using MVC. Switching to MVVM will become much simpler. However, for this special screen, you do not need to refactor other screens that can work well with MVC, because these two architectures are easy to be compatible.

    Make everything as concise and clear as possible, but cannot be simply simplified. -? Albert Stein

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.