A couple of years ago a company hired me for a last moment rescue of an app they were developing for a client of theirs. After months of development the app was late and full of bugs. The goal was fix as many bugs as possible in one week, before we delivered the app to the final client. This was not the first time I had to rescue a project and probably will not be the last. Any freelancer can tell you that, as deadlines get closer, companies sometimes bring in extra power to be able to meet them. As I poked around the code it became clearer and clearer what the source of the problem was.

At the same time it was clearer and clearer that it was not possible to finish this on time. Every time I fixed something, something else broke, in a worse way. Every fix led to more work. After a couple of days I delivered the bad news to my client: “These are not simple bugs. The problem is another: the architecture of the app is completely wrong. The solution would be to rewrite the complete app, but I cannot do this in a such a short time. I can try to fix as many bugs as I can cutting some corners, but this will only solve the symptoms and not the problem. More issues will surface in the future”.

I am the first one to consider a rewrite of an app as the last possible course of action. When you rewrite an app, the work spent on it gets completely thrown away. It is a big waste of time and money, so I always try to save the code if possible. Still, sometimes it is necessary and more efficient to start from scratch. We tried for a week the simple route, but new and harder bugs kept surfacing as predicted.

They were not able to meet the deadline. They apologized to their client and hired an external team to rewrite it, as I had to move to other projects I had scheduled. This of course cost them more money, time and reputation. This was not the first project I had where the only choice was to throw away the entire code base with the thousands of euros spent on it. Unfortunately, this happens regularly.

There are usually many bad programming practices that bring a project to this state. The root cause, though, is usually a complete misunderstanding of the architecture of iOS apps. I already wrote about the basic concepts you should know to develop iOS apps. Now let’s see how they all come together.

At the center of every iOS app we can find the Model-View-Controller pattern. Generally speaking, design patterns should be part of the toolkit of every developer, to avoid situations like the one I described above. MVC is a pattern exists on many different platforms and not only on iOS. The difference though is that in iOS the MVC pattern is at the center of any app you will write. It is not an optional pattern you can chose to avoid: iOS is designed to work with apps written in this way.

The MVC pattern does not draw hard boundaries between its three layers though, and some objects live across these boundaries. At the one between the view layer and the controller layer we find view controllers. View controllers have a central role in iOS and are a required part of any app. Because of their central role, the iOS SDK provides the UIViewController class, which encapsulates the required behavior. Every view controllers has to descend from this class.

A view controller in iOS usually represents and manages a single screen of the app. Every view controller has an associated view, which contains all the user interface elements for the managed screen. In this way, view controllers form the skeleton of your app. If you think about an app as the screens it can have, there will be a view controller for each one of them.

iOS apps have a single window that fits the whole screen of a device, unlike computers, where apps can have multiple windows of different sizes. In the MVC pattern, the window of an app is a view (and indeed is a subclass of UIView in iOS). The views managed by view controllers are added and removed to the window, making the app flow from one screen to the other.

Here is how it happens. First of all, the UIViewController class itself includes functionality to display other view controllers. A view controller can present another view controller modally: the new view controller is placed over the previous one, usually moving in from the bottom of the screen. Although needed, this is a specific and limited way to transition from one view controller to the other. The flow of the screens of an app is usually managed in a different way.

There is a set of special view controllers in iOS, called containers, which have a different role. Instead of managing a single screen of an app, containers manage a set of view controllers. They are responsible of managing the lifecycle of contained view controllers and the transitions between them on the screen. Containers are view controllers themselves, so they also have a managed view. They use this view to display other view controller views as they come on or get off the screen.

This allows us to specify the flow of iOS apps through a sequence of view controllers, without worrying about how to get their views on the screen. A container takes care of this, animating the transitions and managing the lifecycle of each view controller for us.

Any non-trivial app has at least one container view controller. Since containers are view controller themselves, they can also be contained inside other view controllers. This allows to build more complex navigations in your app. A common case, for example, is a tab bar controller containing navigation controllers. In this way you can use the tab bar to switch between sections of your app, while you can navigate the hierarchy of each section with navigation controllers. The Facebook or Twitter apps are two popular apps that use this pattern.

On the other end of the spectrum we have the model layer. Model classes encapsulate the logic of an app they represent the data of the app and the operations on them. They are not concerned about how the user interface behaves. For this reason, they do not know anything about views or controllers. View controllers are responsible for moving the data between the model layer and the view layer. For example, if you are building a social media app, you might have model classes holding the data of users, posts, comments, pictures, videos, etc. These classes would only hold data though, and would not hold code related to presentation, like views, colors, fonts, etc.

Controllers do not only interact with pure model classes though. Sometimes model controllers are necessary to organize and manage the data or interact with different parts of the system. Some examples might include: data sources for table views, managers for network requests or controllers to collect data from the sensors such as the GPS, accelerometers, the compass etc. This is the final picture:

It is important to look what arrows connect and their direction. View controllers communicate pretty much with every object in the chart. For this reason they tend to grow too much and include a lot of code that should go into other classes. They need to communicate with the model to update it and to move data to the view layer. They also need to communicate with the container to trigger the transitions between screens.

Views communicate mainly with view controllers, since they need to receive the data to display and pass events back so that the view controllers can update the model. There is no connection from the views to the model in the MVC version of iOS, even if it is present in other versions of MVC (although, let’s be real. Sometimes it can happen in iOS too). To simplify the passage of data to the views, it is possible to use the another design pattern called Model-View-View-Model. MVVM is just a variant of MVC, so it is not hard to adopt.

The model and model controllers, though, have no arrows “up”. This is because the model of an app needs to be independent from the rest of the architecture. Model classes communicate between them, but have no knowledge about views or view controllers. This way when we change the interface of the app, the model does not change and becomes reusable also in other apps.