The preface

Why focus on architectural design?

Because if you don’t care about architecture, there will come a time when you need to debug several complex things in the same large class, and you will find that it is impossible to quickly find and effectively fix any bugs in that class. Of course, it is difficult to imagine such a class as a whole, so it is possible that some important details will always get lost in the process.

Architectural patterns

The layering of a whole? Logical? Or a clear division of labor? There is no clear definition of architecture mode, but it is abstract, because design depends on architecture. We can understand and analyze it from the perspective of architecture, whether it is the interaction between classes, different small modules, small sections, or even different businesses.

So in terms of the common architectures in iOS, there are these common architectures: MVC architecture, MVP architecture, MVVM architecture

Of course, all of these architectures have one thing in common: decoupling

MVC

What is MVC?

MVC first existed in desktop applications. M refers to business data, V refers to user interface, and C refers to controller. In a specific service scenario, C, as the connection between M and V, is responsible for obtaining the input service data and output the processed data to the interface for display. In addition, when the data is updated, C needs to submit the corresponding update to the interface for display. In the above process, because M and V are completely isolated, when switching business scenarios, you usually only need to replace the corresponding C and reuse the existing M and V to quickly build new business scenarios. Because of its reusability, MVC greatly improves the development efficiency, and has been widely used in all end development.

MVC past

Before we look at Apple’s MVC pattern, let’s look at the traditional MVC pattern.

In the diagram, the View does not have any boundaries, but simply represents changes to the Model in the Controller. Imagine a web page that reloads completely after clicking a link to another page. While this MVC pattern is easy to implement on iOS, it doesn’t help us solve any architectural problems. As it is, all three entities communicate with each other and are tightly coupled. This obviously reduces the reusability of all three, which we don’t want to see.

However, the traditional MVC architecture is not suitable for iOS development today

MVC recommended by Apple — Vision

Since the Controller is a mediator between the View and Model, there is no direct connection between the View and Model. The Controller is a minimum reusable unit, which is good news for us because we always have to find a place to write logically complex code that doesn’t fit in the Model. On paper, this pattern looks intuitive, but do you feel a little weird about it? You’ve even heard of Massive View Controller as an acronym for MVC, and lightening the load of View Controller has become an important topic for iOS developers. If Apple inherited and made some progress with the MVC pattern, why is all this happening?

Apple’s recommended MVC– Facts

Cocoa’s MVC pattern drives people to write bloated View controllers because they are often intermixed into the View lifecycle, so it’s hard to say that the View and ViewController are separate. While it is still possible to convert business logic and data to the Model, most of the time there is nothing we can do when it comes to reducing the load on the View, whose biggest task is to pass user action events to the Controller. The ViewController ultimately takes over all of the agent and data source responsibilities, as well as some of the distribution and cancellation of network requests and other tasks.

You’ll probably see code like this many times:

1varuserCell = tableView.dequeueReusableCellWithIdentifier("identifier") as UserCell
2userCell.configureWithUser(user)Copy the code

This cell, it’s the View that calls the Model directly, so in fact the MVC principle has been violated, but it happens all the time and people don’t even think there’s anything wrong here. If you were strictly following MVC, you would put cell Settings in the Controller and not pass a Model object to the View, which would greatly increase the size of the Controller.

It wasn’t until unit testing that the problems became more apparent. Because your ViewController and View are tightly coupled, it’s hard to test them — you have to be creative enough to simulate views and their life cycles, and while writing View Controllers in such a way, The business logic code is also gradually dispersed into the View layout code.

Let’s look at some simple examples:

import UIKit
struct Person { // Model
    let firstName: String
    let lastName: String
}
class GreetingViewController : UIViewController { // View + Controller
    varperson: 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 MVC
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
view.person = model;Copy the code

This code doesn’t look very testable. We can put everything related to greeting into the GreetingModel and test it separately. But then we can’t test the presentation logic of the page by calling UIView methods (viewDidLoad and didTapButton methods) directly in the GreetingViewController, because that would change the entire page, This is not good news for unit testing. In fact, loading and testing UIView in a single emulator (like the iPhone 4S) does not guarantee that it will work on other devices, so I recommend removing the “Host Application” option from the unit test Target setting and not testing your Application in the emulator.

With all that said, it seems like Cocoa MVC is a pretty poor architectural solution. Let’s evaluate a series of features of MVC:

Task sharing — The View and Model are indeed separate, but the View and Controller are tightly coupled

Testability – Only the Model can be tested due to poor dispersion

Ease of use – Minimum amount of code compared to the other modes. There are a lot of people familiar with it, so it’s easy to maintain even for less experienced developers.

Cocoa MVC is definitely the best solution if you don’t want to put a lot of effort into your architectural choices, and you’ll find that some of the other high-maintenance patterns are a death blow to the small applications you’re developing.

MVP

The downside of MVC is that it doesn’t distinguish between business logic and business presentation, which is unfriendly to unit testing. Mvcp. M and V functions remain the same. The original C is now only responsible for layout, and all logic has been moved to the P layer.

The MVP implements Cocoa’s MVC vision

Doesn’t this look like apple’s MVC solution? Yes, the pattern is called MVC, but does that mean apple’s MVC is actually the MVP? No, it’s not. If you recall, the View is tightly coupled to the Controller, but MVP’s coordinator Presenter does nothing to change the ViewController’s life cycle, so the View can be easily simulated. There is no layout code in Presenter, but it is responsible for updating the View’s data and state. In terms of MVP, the UIViewController subclass is actually Views not Presenters. This difference greatly improves the testability of this pattern, at the cost of some reduction in development speed, since some manual data and event binding must be done, as you can see in the following example:

import UIKit
struct Person { // Model
    let firstName: String
    let lastName: String
}
protocol GreetingView: class {
    func setGreeting(greeting: String)
}
protocol GreetingViewPresenter {
    init(view: GreetingView, person: Person)
    func showGreeting()
}
class 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)
    }
}
class GreetingViewController : UIViewController, GreetingView {
    varpresenter: 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 MVP
let model = Person(firstName: "David", lastName: "Blaine")
let view = GreetingViewController()
let presenter = GreetingPresenter(view: view, person: model)
view.presenter = presenterCopy the code

MVP was the first architectural pattern for how to coordinate the integration of three essentially separate layers, and since we don’t want the View to involve the Model, the logic for dealing with this coordination in the View Controller is incorrect, so we need to do it somewhere else. For example, we can do an app-wide routing service that performs coordination tasks and view-to-view displays. This problem arises and must be addressed not only in MVP mode, but also in the following centralized scenarios.

Let’s evaluate the MVP’s list of characteristics:

Task sharing – We split the most important tasks into Presenter and Model, and View has less functionality (although Model doesn’t have many tasks in the example above).

Testability – Very good, because it is easy to test most business logic thanks to a simple View layer

Ease of use – In our unrealistically simple example above, twice as much code as MVC, but the MVP concept is very clear

MVP- Binding and signaling

There are other MVPS — MVPS for monitoring controllers.

This variant includes a direct binding between the View and Model, but the Presenter still manages action events from the View and can also perform updates to the View.

But as we’ve learned before, vague responsibilities can be a terrible thing to do, not to mention tightly linking the View to the Model. This is somewhat similar to the principles of desktop development in Cocoa.

MVVM

The MVVM architecture is the most recent addition to the MV(X) family, so let’s hope it takes into account the problems that have already occurred in the MV(X) family. On a theoretical level MVVM looks good, and we are already very familiar with Views and Models, as well as Meditor, which in MVVM is the View Model.

It looks a lot like the MVP mode:

MVVM treats the ViewController as a View

There is no tight connection between the View and Model

In addition, it has the same binding functionality as the MVP in the supervised version, but the binding is not between View and Model but between View and ViewModel.

What does a ViewModel actually stand for in iOS? It’s basically every control under UIKit and the state of that control. The ViewModel call changes the Model and updates the Model changes to itself and since we’re bound to the View and ViewModel, the first step is to update the state accordingly.

The binding

I already mentioned this in the MVP section, but we will continue to discuss this section.

If we don’t want to do it ourselves, then we have two options:

Kvo-based binding libraries such as RZDataBinding and SwiftBond

Fully functional responsive programming such as ReactiveCocoa, RxSwift, or PromiseKit

In fact, especially recently, when you hear MVVM, you think of ReactiveCoca and vice versa. While it is possible to use MVVM with a simple binding, ReactiveCocoa makes much better use of MVVM.

But there is a truth to be said about this framework: great power comes from great responsibility. When you start using Reactive there’s a lot of potential to screw things up. In other words, if a bug is found, debugging it can take a lot of time. Take a look at the function call stack:

In our simple example, the FRF framework and KVO are transiently disabled, and instead we call the showGreeting method directly to update the ViewModel and use the properties via the greetingDidChange callback.

import UIKit struct Person { // Model let firstName: String let lastName: String } protocol GreetingViewModelProtocol: class { vargreeting: String? { get } vargreetingDidChange: ((GreetingViewModelProtocol) -> ())? { get set } // function to call when greeting did change init(person: Person) func showGreeting() } class GreetingViewModel : GreetingViewModelProtocol { let person: Person vargreeting: String? { didSet { self.greetingDidChange? (self) } } vargreetingDidChange: ((GreetingViewModelProtocol) -> ())? required init(person: Person) { self.person = person } func showGreeting() { self.greeting = "Hello"+ " "+ self.person.firstName + " "+ self.person.lastName } } class GreetingViewController : UIViewController { varviewModel: 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 MVVM let model = Person(firstName: "David", lastName: "Blaine") let viewModel = GreetingViewModel(person: model) let view = GreetingViewController() view.viewModel = viewModelCopy the code

Let’s also evaluate a series of characteristics of MVVM:

Task sharing – It’s not very clear in the example, but in fact the MVVM View takes more responsibility than the MVP View. Because the former updates status through the ViewModel’s Settings binding, the latter only listens for Presenter events but does not update itself.

Testability — The ViewModel doesn’t know anything about the View, which allows us to easily test the ViewModel. Views can also be tested, but due to the UIKit category, testing them is usually ignored.

Ease of use – The amount of code in our example is similar to that of the MVP, but in real development, we would have to point events in the View to Presenter and manually update the View. If bindings were used, the amount of MVVM code would be much smaller.

conclusion

We’ve looked at the centralized architecture pattern, and hopefully you’ve figured out what’s bothering you. As you have no doubt learned from reading this article, there is no such thing as a silver bullet. Therefore, choosing an architecture is a process of analyzing the pros and cons according to the actual situation.

Therefore, there are multiple architectures within the same application. For example, you might start with MVC, then suddenly realize that a page is getting harder to maintain in MVC mode, and switch to MVVM architecture, but only for that one page. There is no need to refactor pages that work well in MVC patterns because both can coexist.