1. 程式人生 > >iOS Architecture Patterns

iOS Architecture Patterns

MV(X) essentials

Nowadays we have many options when it comes to architecture design patterns:

First three of them assume putting the entities of the app into one of 3 categories:

  • Models — responsible for the domain data or a data access layer which manipulates the data, think of ‘Person’ or ‘PersonDataProvider
    ’ classes.
  • Views — responsible for the presentation layer (GUI), for iOS environment think of everything starting with ‘UI’ prefix.
  • Controller/Presenter/ViewModel — the glue or the mediator between the Model and the View, in general responsible for altering the Model by reacting to the user’s actions performed on the View
    and updating the View with changes from the Model.

Having entities divided allows us to:

  • understand them better (as we already know)
  • reuse them (mostly applicable to the View and the Model)
  • test them independently

Let’s start with MV(X) patterns and get back to VIPER later.

MVC

How it used to be

Before discussing Apple’s vision of MVC let’s have a look on the

traditional one.

Traditional MVC

In this case, the View is stateless. It is simply rendered by the Controller once the Model is changed. Think of the web page completely reloaded once you press on the link to navigate somewhere else. Although it is possible to implement the traditional MVC in iOS application, it doesn’t make much sense due to the architectural problem — all three entities are tightly coupled, each entity knows about the other two. This dramatically reduces reusability of each of them — that is not what you want to have in your application. For this reason, we skip even trying to write a canonical MVC example.

Traditional MVC doesn't seems to be applicable to modern iOS development.

Apple’s MVC

Expectation

Cocoa MVC

The Controller is a mediator between the View and the Model so that they don’t know about each other. The least reusable is the Controller and this is usually fine for us, since we must have a place for all that tricky business logic that doesn’t fit into the Model.

In theory, it looks very straightforward, but you feel that something is wrong, right? You even heard people unabbreviating MVC as the Massive View Controller. Moreover, view controller offloading became an important topic for the iOS developers. Why does this happen if Apple just took the traditional MVC and improved it a bit?

Apple’s MVC

Reality

Realistic Cocoa MVC

Cocoa MVC encourages you to write Massive View Controllers, because they are so involved in View’s life cycle that it’s hard to say they are separate. Although you still have ability to offload some of the business logic and data transformation to the Model, you don’t have much choice when it comes to offloading work to the View, at most of timesall the responsibility of the View is to send actions to the Controller. The view controller ends up being a delegate and a data source of everything, and is usually responsible for dispatching and cancelling the network requests and… you name it.

How many times have you seen code like this:

The cell, which is the View configured directly with the Model, so MVC guidelines are violated, but this happens all the time, and usually people don’t feel it is wrong. If you strictly follow the MVC, then you supposed to configure the cell from the controller, and don’t pass the Model into the View, and this will increase the size of your Controller even more.

Cocoa MVC is reasonably unabbreviated as the Massive View Controller.

The problem might not be evident until it comes to the Unit Testing (hopefully, it does in your project). Since your view controller is tightly coupled with the view, it becomes difficult to test because you have to be very creative in mocking views and their life cycle, while writing the view controller’s code in such a way, that your business logic is separated as much as possible from the view layout code.

Let’s have a look on the simple playground example:

MVC example
MVC assembling can be performed in the presenting view controller

This doesn’t seem very testable, right? We can move generation of greeting into the new GreetingModel class and test it separately, but we can’t test any presentation logic (although there is not much of such logic in the example above) inside the GreetingViewController without calling the UIView related methods directly (viewDidLoad, didTapButton) which might cause loading all views, and this is bad for the unit testing.

In fact, loading and testing UIViews on one simulator (e.g. iPhone 4S) doesn’t guarantee that it would work fine on the other devices (e.g. iPad), so I’d recommend to remove “Host Application” from your Unit Test target configuration and run your tests without your application running on simulator.

With all that said, it might seems that Cocoa MVC is a pretty bad pattern to choose. But let’s assess it in terms of features defined in the beginning of the article:

  • Distribution —the View and the Model in fact separated, but the View and the Controller are tightly coupled.
  • Testability — due to the bad distribution you’ll probably only test your Model.
  • Ease of use — the least amount of code among others patterns. In addition everyone is familiar with it, thus, it’s easily maintained even by the unexperienced developers.

Cocoa MVC is the pattern of your choice if you are not ready to invest more time in your architecture, and you feel that something with higher maintenance cost is an overkill for your tiny pet project.

Cocoa MVC is the best architectural pattern in terms of the speed of the development.

MVP

Cocoa MVC’s promises delivered

Passive View variant of MVP

Doesn’t it look exactly like the Apple’s MVC? Yes, it does, and it’s name is MVP (Passive View variant). But wait a minute… Does this mean that Apple’s MVC is in fact a MVP? No, its not, because if you recall, there, the View is tightly coupled with the Controller, while the MVP’s mediator, Presenter, has nothing to do with the life cycle of the view controller, and the View can be mocked easily, so there is no layout code in the Presenter at all, but it is responsible for updating the View with data and state.

What if I told you, the UIViewController is the View.

In terms of the MVP, the UIViewController subclasses are in fact the Views and notthe Presenters. This distinction provides superb testability, which comes at cost of the development speed, because you have to make manual data and event binding, as you can see from the example:

MVP example

Important note regarding assembly

The MVP is the first pattern that reveals the assembly problem which happens due to having three actually separate layers. Since we don’t want the View to know about the Model, it is not right to perform assembly in presenting view controller (which is the View), thus we have to do it somewhere else. For example, we can make the app-wide Router service which will be responsible for performing assembly and the View-to-View presentation. This issue arises and has to be addressed not only in the MVP but also in all the following patterns.

Let’s look on the features of the MVP:

  • Distribution — we have the most of responsibilities divided between the Presenter and the Model, with the pretty dumb View (in the example above the Model is dumb as well).
  • Testability — is excellent, we can test most of the business logic due to the dumb View.
  • Easy of use — in our unrealistically simple example, the amount of code is doubled compared to the MVC, but at the same time, idea of the MVP is very clear.
MVP in iOS means superb testability and a lot of code.

MVP

With Bindings and Hooters

There is the other flavour of the MVP — the Supervising Controller MVP. This variant includes direct binding of the View and the Model while the Presenter (The Supervising Controller) still handles actions from the View and is capable of changing the View.

Supervising Presenter variant of the MVP

But as we have already learned before, vague responsibility separation is bad, as well as tight coupling of the View and the Model. That is similar to how things work in Cocoa desktop development.

Same as with the traditional MVC, I don’t see a point in writing an example for the flawed architecture.

MVVM

The latest and the greatest of the MV(X) kind

The MVVM is the newest of MV(X) kind thus, let’s hope it emerged taking into account problems MV(X) was facing previously.

In theory the Model-View-ViewModel looks very good. The View and the Model are already familiar to us, but also the Mediator, represented as the View Model.

MVVM

It is pretty similar to the MVP:

  • the MVVM treats the view controller as the View
  • There is no tight coupling between the View and the Model

In addition, it does binding like the Supervising version of the MVP; however, this time not between the View and the Model, but between the View and the View Model.

So what is the View Model in the iOS reality? It is basically UIKit independent representation of your View and its state. The View Model invokes changes in the Model and updates itself with the updated Model, and since we have a binding between the View and the View Model, the first is updated accordingly.

Bindings

I briefly mentioned them in the MVP part, but let’s discuss them a bit here. Bindings come out of box for the OS X development, but we don’t have them in the iOS toolbox. Of course we have the KVO and notifications, but they aren’t as convenient as bindings.

So, provided we don’t want to write them ourselves, we have two options:

In fact, nowadays, if you hear “MVVM” — you think ReactiveCocoa, and vice versa. Although it is possible to build the MVVM with the simple bindings, ReactiveCocoa (or siblings) will allow you to get most of the MVVM.

There is one bitter truth about reactive frameworks: the great power comes with the great responsibility. It’s really easy to mess up things when you go reactive. In other words, if you do something wrong, you might spend a lot of time debugging the app, so just take a look at this call stack.

Reactive Debugging

In our simple example, the FRF framework or even the KVO is an overkill, instead we’ll explicitly ask the View Model to update using showGreeting method and use the simple property for greetingDidChange callback function.

MVVM example

And again back to our feature assessment:

  • Distribution — it is not clear in our tiny example, but, in fact, the MVVM’s View has more responsibilities than the MVP’s View. Because the first one updates it’s state from the View Model by setting up bindings, when the second one just forwards all events to the Presenter and doesn’t update itself.
  • Testability — the View Model knows nothing about the View, this allows us to test it easily. The View might be also tested, but since it is UIKit dependant you might want to skip it.
  • Easy of use — its has the same amount of code as the MVP in our example, but in the real app where you’d have to forward all events from the View to the Presenter and to update the View manually, MVVM would be much skinnier if you used bindings.
The MVVM is very attractive, since it combines benefits of the aforementioned approaches, and, in addition, it doesn’t require extra code for the View updates due to the bindings on the View side. Nevertheless, testability is still on a good level.