Five different App design patterns

  • The standard Cocoa Model-View-Controller (MVC) is the design pattern Apple adopted in the sample project. It is the most common architecture in Cocoa Apps, and it is the baseline used when discussing architectures in Cocoa.
  • Model-view-viewmodel + Coordinator (MVVM-C) is a variant of MVC that has a separate “view-Model” and a coordinator to manage the View Controller. MVVM uses data binding (often used in conjunction with responsive programming) to establish the connection between the View-Model layer and the View layer.
  • Model-view-controller +ViewState (MVC+VS) This mode puts all View states in one place, rather than having them scattered between the View and View Controller. This follows the same rules as the Model layer
  • Model Adapter-ViewBinder (MAVB) MAVB focuses on building declarative views and uses bindings to communicate between Models and views instead of controllers.
  • The Elm architecture (TEA) is the opposite of a common architecture like MVC or MVVM. It uses the virtual view hierarchy to build the view and uses Reducer to interact between the Model and the view.

Model-View-Controller

The Controller layer receives all view actions, handles all interaction logic, sends all Model actions, receives all Model notifications, prepares all data for presentation, and finally applies them to changes to the View.

The dotted lines in the figure represent runtime references; neither the View layer nor the Model layer directly references the Controller in code. The solid lines represent compile-time references, and the controller instance knows the interface of the View and Model objects to which it is connected.

build

The App object is responsible for creating the topmost View Controller, which will load the view and know what data to retrieve from the Model and display it. The Controller either explicitly creates and holds the Model layer, or obtains the Model through a delay-created Model singleton. In a multi-document configuration, the model layer is owned by a lower-level document such as UIDocument or NSDocument. The individual Model objects associated with the View are usually referenced and cached by the Controller.

To change the Model

In MVC, the controller receives view events primarily through the Target/Action mechanism and a delegate (set by the storyboard or code). The Controller knows which view it is connected to, but the view has no information about the Controller interface at compile time. When a View event arrives, the controller has the ability to change its internal state, change the model, or change the View hierarchy directly.

Change the View

In our understanding of MVC, when a view action changes the Model, the controller should not directly manipulate the View hierarchy. Instead, the controller subscribes to the Model notifications and changes the View hierarchy when the notifications arrive. In this way, the data flow can go one way: The View action is converted to a Model change, and the Model sends a notification, which is converted to a View change.

View State

View state can be stored in View or Controller properties as needed. In contrast to view actions that affect the Model, actions that only affect the state of the View or controller do not need to be passed through the Model. The storage of view state can be implemented using a combination of storyboard, which is responsible for recording the active controller hierarchy, and UIStateRestoring. UIStateRestoring is responsible for reading data from the controller and view.

test

In MVC, the View Controller is tightly connected to the rest of the app. The absence of boundaries makes it difficult to write unit and interface tests for controllers, and integration testing is one of the few remaining viable testing tools. In integration testing, we build the connected View, Model, and Controller layers, and then manipulate the Model or view to see if we get the results we want.

The Model – View – ViewModel + coordinator

MVVM, like MVC, is based on an architecture based on scenes (subtrees in the view hierarchy that may be dropped in or swapped out when navigation changes).

Compared to MVC, MVVM uses view-Model in each scenario to describe the presentation logic and interaction logic in the scenario.

The view-Model does not contain a reference to the View or controller at compile time. It exposes a set of properties that describe what value each view should have when it is displayed. By applying a series of transformations to the underlying Model object, you get these values that you can eventually set directly to the View. The actual work of setting these values to the view is done by pre-created bindings that ensure that when the display values change, they are set to the corresponding view. Reactive programming is a great tool for expressing such declarations and transformations, so it is naturally appropriate (though not strictly necessary) to work with view-Models. In many cases, the entire View-Model can be expressed declaratively in the form of a responsive programming binding.

In theory, because the View-Model contains no reference to the View layer, it is independent of the App framework, which allows testing of the View-Model to be independent of the App framework as well.

Since the View-Model is coupled to the scene, we also need an object that can provide logic when switching between scenes. In MVVM -c, this object is called a coordinator. The coordinator holds a reference to the Model layer and understands the structure of the View Controller tree so that it can provide the required Model objects for each scenario’s View-Model.

Unlike MVC, the VIEW Controller in MVVM-c never directly references another View controller (and therefore, no other View-Model). The View Controller communicates the View action information to the coordinator through the delegate mechanism. The coordinator displays the new View Controllers and sets their model data. In other words, the view controller hierarchy is managed by the coordinator, not by the View Controller.

The overall structure of the architecture created by these features is shown below:

If we ignore the coordinator, the diagram looks a lot like MVC, but with a phase between the View Controller and the Model. MVVM moves most of the work previously done in the View Controller to the View-Model, but note that the View-Model does not have a reference to the View Controller at compile time.

The View-Model can be separate from the View Controller and View, and can be tested separately. Also, the View Controller no longer has internal View states, and these states have been moved to the View-Model. In MVC, the dual role of the View Controller (both as part of the View hierarchy and responsible for coordinating the interaction between the View and the Model) has been reduced to a single role (the View controller is just a part of the View hierarchy).

The addition of the coordinator pattern further reduces the view Controller’s responsibility: it now doesn’t have to worry about presenting other View controllers. So this actually reduces the coupling between view controllers at the expense of adding a layer of controller interfaces.

build

For model creation and unchanged in MVC, it is usually the responsibility of a top-level controller. However, the individual Model object now belongs to the View-Model, not the View Controller. The creation of the initial view hierarchy is done by storyboard or code just like in MVC. Unlike MVC, the View Controller no longer directly gets and prepares data for each view, it hands that job over to the Viewmodel. The View Controller creates the view-Model along with the view-model, and binds each View to the corresponding property exposed by the view-Model.

To change the Model

In MVVM, the View Controller receives View events in the same way as in MVC (and establishes a connection between the View and the View Controller in the same way). However, when a View event arrives, the View Controller does not change its internal state, view state, or model. Instead, it immediately calls the method on the View-Model, which in turn changes the internal state or model.

Change the View

Unlike MVC, the View Controller doesn’t listen to the Model. The View-Model is responsible for observing the Model and transforming the Model’s notifications into a form that the View Controller can understand. The View Controller subscribes to changes to the View-Model, usually through a responsive programming framework, but can also use any other observation mechanism. When a view-Model event arrives, it is up to the View Controller to change the view hierarchy.

To achieve one-way data flow, the View-Model should always send the View action that changes the Model to the Model, and only notify the relevant observers after the model change has actually occurred.

View State

The View state exists either in the View itself or in the View-Model. Unlike MVC, view Controller doesn’t have any View state. Changes to the View state in the view-Model are observed by the controller, but the controller cannot distinguish between notification of model changes and notification of View state changes. When using a coordinator, the View Controller hierarchy is managed by the coordinator.

test

Because the View-Model and view layer are decoupled from the Controller layer, you can use interface tests to test the View-Model instead of using integration tests as in MVC. Interface tests are much simpler than integration tests because there is no need to build a full component hierarchy for them.

In order for the interface test to cover as much scope as possible, the View Controller should be as simple as possible, but those parts that have not been removed from the View Controller still need to be tested separately. In our implementation, this includes the interaction with the coordinator and the initial code responsible for creating the work.