Model-View-Controller (MVC) in iOS – A Modern Approach

Learn some tips and tricks to go one up on MVC, Apple’s recommended architecture pattern, and modify your code to be scalable and extensible! By Felipe Laso-Marsetti.

Leave a rating/review
Download materials
Save for later
Share
Update note: Felipe Laso-Marsetti updated this tutorial for Swift 4.2, Xcode 10 and iOS 12. Rui Peres wrote the original.

Getting Started

As a new iOS developer, there is a huge amount of information you need to master: a new language, new frameworks and APIs, and Apple’s recommended architectural pattern Model-View-Controller, or MVC for short.

The MVC pattern is commonplace in iOS development. While it’s not a difficult pattern to understand and get familiar with, there are things to consider to avoid common pitfalls.

Getting up to speed with iOS development can be a daunting task and, more often than not, MVC doesn’t get as much attention as the APIs or programming language. This can lead to major architectural problems in your apps down the road.

This tutorial will help you avoid common mistakes that lead to apps that are too difficult to extend. You’ll learn a good approach of what (and what not) to do as you use MVC to build out your app using best practices, learned through the years of iOS development.

In this tutorial you won’t be doing any coding. Instead, you will walk through a project that uses MVC with best practices in mind.

Use the Download Materials button at the top or bottom of this tutorial to download the sample project.

What is MVC

With the project handy, it’s time to start learning about the Model, the View and the Controller. But first, what exactly is MVC?

Note: If you already know the concept of MVC, feel free to skip ahead to the next section, where you’ll start getting into best practices.

MVC is a software development pattern made up of three main objects:

  • The Model is where your data resides. Things like persistence, model objects, parsers, managers, and networking code live there.
  • The View layer is the face of your app. Its classes are often reusable as they don’t contain any domain-specific logic. For example, a UILabel is a view that presents text on the screen, and it’s reusable and extensible.
  • The Controller mediates between the view and the model via the delegation pattern. In an ideal scenario, the controller entity won’t know the concrete view it’s dealing with. Instead, it will communicate with an abstraction via a protocol. A classic example is the way a UITableView communicates with its data source via the UITableViewDataSource protocol.

When you put everything together, it looks like this:

MVC Diagram

Each one of these objects is meant to be separate from the other, and each fulfills a specific role. When building an app that uses a specific pattern, in this case MVC, the goal is to adhere to the pattern when building all layers of your app.

Apple’s MVC documentation explains these layers in detail and can give you a good theoretical understanding. From a practical perspective, though, it falls a little short.

In this tutorial, you’ll learn to treat MVC as a pattern and not as a strict rule that you must never break. As is the case with many things in software development, nothing is perfect and neither is MVC. There are gray areas that you’ll run into, but no decision you make is the wrong decision. As long as you don’t have files that are too big, or code that is difficult to expand upon, then you’re likely doing a good job. Use MVC — and other patterns for that matter — as architectural guidelines and foundations for your app.

Having said that, it’s time to look at each layer in more detail.

The Model (M)

The model layer encompasses your app’s data. No surprise there, but there are usually other classes and objects in your projects that you can include in this layer:

  • Network code: You preferably only use a single class for network communication across your entire app. It makes it easy to abstract concepts common to all networking requests like HTTP headers, response and error-handling and more.
  • Persistence code: You use this when persisting data to a database, Core Data or storing data on a device.
  • Parsing code: You should include objects that parse network responses in the model layer. For example, in Swift model objects, you can use JSON encoding/decoding to handle parsing. This way, everything is self-contained and your network layer doesn’t have to know the details of all your model objects in order to parse them. Business and parsing logic is all self-contained within the models.
  • Managers and abstraction layers/classes: Everyone uses them, everyone needs them, nobody knows what to call them or where they belong. It’s normal to have the typical “manager” objects that often act as the glue between other classes. These can also be wrappers around lower-level, more robust API: a keychain wrapper on iOS, a class to help with notifications or a model to help you work with HealthKit.
  • Data sources and delegates: Something that may be less common is developers relying on model objects to be the data source or delegate of other components such as table or collection views. It’s common to see these implemented in the controller layer even when there’s a lot of custom logic that’s best kept compartmentalized.
  • Constants: It’s good practice to have a file with constants. Consider putting these within a structure of structures or variables. That way, you can reuse storyboard names, date formatters, colors, etc.
  • Helpers and extensions: In your projects, you will often add methods to extend the capabilities of strings, collections, etc. These too can be considered part of the model.

There are more classes and objects you could include, but these seem to be the most common. Again the goal is not to focus too much on the semantics of what is or isn’t a model. Rather, it is to have a solid foundation upon which to get your work done.

From a testing perspective, the model is an excellent candidate. You can confirm business logic, mock some of your networking or persistence methods or add tests around sensitive pieces of your model layer.