When the iPhone was only the third, I saw this diagram in the beginning of a very first tutorial about iOS programming on the Apple Developer portal:
(A similar article still exists and the diagram is there: Cocoa Core Competencies. Model-View-Controller)
This diagram presents the basic iOS architectural pattern — MVC. 10 years ago I understood this diagram in a very simple way: UIView, UIScrollView or UITableView is the view on that diagram. As a developer, I arrange them on a scene in the Interface Builder, bind them with my source code — outlets and actions defined in my view-controller. The last one is exactly the controller from the MVC diagram. My application stores a user data, for example, in a text file or, in a more complicated case, via Core Data. Classes allowing access to this data is the model from the diagram.
One more thing to add: no business logic code in the model, the model does not communicate with the view. The view-controller is responsible to create all the views and the model and perform the communication between them.
The behavior of such simple system is clear: the user interacts with the view and his actions are passed to the view-controller. The last one performs a calculation, changes its own state and this state is reflected in the view. If the user action requires a change in the model, the view-controller calls the model to change. If the model changes, the view-controller is notified of this change. And the view-controller is the one, updating the view.
This is a simplified or even naive understanding of this fundamental pattern. But it allows developing simple applications such as calculator or painter. I’d like to show an approach to develop and maintain more complicated applications.
For the beginning let’s transform the diagram above and draw it in a layered style.
It reminds a familiar common application architecture diagram known from the university time:
The layer was a very fashionable term. The layered style was everywhere including the application architecture. All my projects at that time began from this diagram with three application layers: User Interface, Business Logic, and Data. All application objects belong to one of these layers. The data layer and the user interface do not communicate but through the business logic.
Let’s complicate that layered MVC diagram. View-controller creates the model and calls it if a change happens and it is shown as an arrow on the diagram. An arrow goes from the view-controller to the view — the view-controller create all views and updates them if its state or the model changes. The model knows nothing about the view-controller and if a change happens in the model it posts a notification about it or, more often, the view-controller observes the model changes. It happens in the run-time and I’d prefer to use a dashed line to draw this arrow from the model to the view-controller.
The dashed arrow also should show a connection from the view to the view-controller: the user interacts with the view in the run-time but the view does not call the view-controller directly, the view does not know the view-controller interface and it passes the user actions to the view-controller via Target-Action Design pattern.
This is the simplest or an ideal form of the MVC that fits for a small application constructed with only one view-controller. Let’s add one more view-controller.
For example, click on a button in the view-controller (the original schema with one view-controller above) opens a new view-controller: the original view-controller contains an action method in which I’m creating and initializing another view-controller, which creates its own views and model and manages them.
There are two connection arrows between the view-controllers on the diagram:
- the original view-controller creates and initializes the secondary view-controller
- the secondary view-controller transmits a data (user input or an action), to the first view-controller — Delegate Design pattern applied. This data transition happens in the run-time and so drawn by the dashed line.
Let’s say that the second view-controller from the diagram contains a special function downloading a picture or a data file from the internet:
This application itself is simple: two view-controllers and a downloading function. The diagram is not difficult also. But it shows a heavy connection between the view-controllers. One more thing visible on the diagram — the second view-controller has a special service function inside. You can imagine how the entire source code looks like: few #import lines at the beginning of the file of the original view-controller, there is a code that initializes the second view-controller, and the second view-controller has a method downloading an image.
I can propose to add more view-controllers with other special service methods, but even now you see the spaghetti code — not testable, not maintainable.
How many my projects use MVC exactly in this way? Few demos I made myself, few prototypes, few proofs of concepts,… — the goal was to test something, to demonstrate something and I was not going to release it anyway, I never had plans to maintain such projects.
What to do with a real long-life AppStore project?
What is a modern architecture for a commercial project?
I’m beginning the refactoring of that application with two view-controllers from the simplest problem: that image downloading method is moved to a separated class named, for example, DownloadController. In the view-controller, I’m adding a variable of type DownloadController and in the place the image downloading happens, I will call this new controller. This is the well-known Composition Design pattern applied. The code is more decoupled and more reusable:
Moving such things as this downloading procedure out of view-controllers, make them separate and composable, is a common practice and a good way to avoid the well-known problem of Massive View-Controller.
It’s the first step. The second step is to break the coupling between the view-controllers. Let’s add a new class Coordinator to the project. This class creates all application view-controllers, initializes them and perform all transitions between them. In the place where the first view-controller was creating and displaying the second view-controller, an appropriate method of the Coordinator is called. The Delegation Design pattern is used to pass this action from the first view-controller to the Coordinator. The source code of the Coordinator with two view-controllers can look so:
The application diagram is transformed and now looks so:
Let’s make the third step — go back to the DownloadController and make it inherit a protocol which declares a function that will download an image. Let’s create the instance of the DownloadController in the coordinator whenever it is required and pass it to the second view-controller. The last one will not depend on the DownloadController class but on this Download protocol.
The current state of the application architecture is shown on the diagram below:
Is this diagram more complicated than the one above with two view-controllers and the downloading procedure? Probably. But the source code of the project is cleaner, more maintainable, reusable and testable.
Modal-View-Controller with Coordinator plus few other famous design patterns such as Composition and Dependency Injection allow to develop modern complicated applications and avoid Massive view-controller syndrome.