The MVVM Pattern for iOS Apps in Swift: a Pragmatic Approach

The MVVM Pattern for iOS Apps in Swift: a Pragmatic Approach (reference vs. value types, networking, and reactive frameworks)

The Model-View-VievModel pattern has recently become of fashion in the iOS development community.

But it’s not without its controversies.

I, for one, I have my reservations the classic implementation of MVVM as an architectural design pattern. Moreover, in SwiftUI, MVC and MVVM are practically the same pattern.

But I also think that some of its ideas are sound.

When talking about the shortcomings, I don’t take this position only from the theory.

I have experienced them in real apps.

I worked on some projects that followed some of these ideas. One of these projects was from a quite big and known company. That project was years late in respect to the planned release date.


Think about that.

While I cannot say that there was only one cause, they definitely included practices related to the MVVM pattern.

In this article, we will have a look at:

  • the relationship between MVVM and MVC
  • the details of the MVVM pattern
  • where to put networking code in MVVM
  • MVVM and functional reactive programming frameworks likeRxSwift
  • the advantages and disadvantages of different approaches
  • a pragmatic approach that avoids the many of the problems, with a concrete example

Architecting SwiftUI apps with MVC and MVVM


In the MVC pattern, views should not know anything about the model of an app

Let’s start from the actual problem we are trying to solve.

One of the most common violations of the MVC pattern I see in iOS apps happens when views are coupled to the model types of an app.

Like any major mistake, I made this one too in the past. So I know well how it looks like.

I have often pointed to the separate layers of the MVC pattern in my articles. I can link most of the problems in iOS apps can to the violation of these principles.

As a refresher, the iOS MVC pattern divides our types into four (and not just three) layers:

  • model types represent data and its business logic;
  • model controllers interface with the device and deal with model types in app specific ways. Examples are keeping the app’s state, storing data on disk or accessing the network;
  • views display information on the screen and allow user interaction;
  • view controllers are a bridge between views and the lower layers of the MVC pattern. They pass the data to display to the views and interpret user interaction, dictating the flow of the screens of an app.

The basic structure of the MVC pattern in iOS

I wrote extensively about this already.

As we see in the points above, views need to receive data to display from view controllers. They don’t access the model directly.

Bridging between model types and views comes with its own problems

The problems in passing data to views in a view controller come from the fact that it is too easy to:

  • pass model types directly to a view to render on the screen; or
  • make the view controller know too much about the internal implementation of a view

Developers often do either (or both) because they are quick solutions.

But both cause problems because they create coupling between types. The first creates coupling between views and the model types. The second between views and view controllers.

Coupling makes our types both harder to reuse and easier to break.

Let’s have a look at both problems in more detail.

Developers often pass a model type directly to a view so that the view can read format such data by itself. But if you look at the roles in MVC, formatting data is not a responsibility for views.

This creates coupling between a view and a model type, which means that:

  • the view is less reusable across different screens and less so across different apps. If you want to use the same view to display another data type, things get complicated. The view needs to know about two different types and to discern between them. If you want to reuse such view in other projects, you need to bring along all these dependencies;
  • the view implementation is tied to the implementation of the model. This means that any change in the model can break the code of the view, which then needs to be rewritten. This might not be so trivial, especially if a view depends on more than one model type.

The MVC pattern solves this problem making views independent from the model types. In the scheme for the iOS version of MVC, there is no arrow between the view layer and the model layer.

This means that views should get data only in simple formats.

In fact, if you look at all the UIKit views, they work this way. Labels get simple strings to display. Switches, sliders and progress indicators get simple types such as booleans, integers, and floating point numbers.

Complex views like table views are instead attached to a data source. To this, they ask cells to display. Notice that a table view does not ask for data. It asks for an already populated cell.

In this way, a table view can stay generic and you can use it for any model type in your apps.

Views should not expose their internal implementation

The other violation of the MVC pattern happens when view controllers or other objects know too much about the internal implementation of a view.

You can see this often in a table view data source.

A data source needs to populate a cell before returning it to the table view.

A common mistake is making the data source access the subviews of a cell to populate them with data.

So data sources often access labels, image views and other subviews of a cell to set their values. This happens either through outlets or, even worse, using the viewWithTag() method of UIView.

You can even find both in Apple’s documentation.

The problem here is that this again introduces coupling. When we change the implementation of a cell, we break all the data sources that access it.

Labels and other subviews are part of the internal implementation of a view. No object should depend on the internal implementation of another one.

In fact, outlets should not be even exposed. I explicitly mark all the outlets in my code as private. The owner of an outlet should be the only one accessing it.

The solution here is the same as above. A cell is a view like any other one. As such, it should receive data in a simple format. It is then its own responsibility to populate outlets.

Data conversion in view controllers is not optimal

So the guidelines about views are:

  • don’t pass model types to them
  • don’t access their internal implementation

And as we have seen the solution to follow both is the same: a view should accept data using simple types.

So we need to transform data from model types into simple types.

And where does this transformation usually end up?

Exactly: inside view controllers.

The temptation sometimes is to put it into model types. But model types should not know anything about how data is presented to the user.

The same data can have different representations in different parts of the app. This would give too many responsibilities to model types and violate the MVC pattern.

Model controllers are not the right place either. They should deal only with data and not its representation.

This leaves view controllers, which is a problem.

To be clear, this is not that wrong. If you follow the above guidelines for views and keep the responsibilities defined by MVC separate, you are already much further ahead than many copy-and-paste developers.

But putting data transformation in view controllers is not optimal.

  • For one, we know that code in view controllers tends to grow a lot, because of their central role in iOS. So it’s better to avoid contributing to the problem, when possible.
  • This also impacts code reuse. If we want to use the same view in another screen, we need to find a way to share the transformation code among view controllers.

There are some solutions to this.

For example, we can use Swift extensions to group related transformation code. This is a valid approach.

The other approach is following the Model-View-ViewModel pattern.

The MVVM pattern is an extension of the MVC pattern

The Model-View-ViewModel pattern was developed by, of all companies, Microsoft.

Yeah, I know. Shocking.

MVVM derives from the Model-View-Controller pattern. It aims to solve the problem of having the transformation of model objects happen inside view controllers.

The idea of MVVM is very simple.

We add a new layer to the MVC pattern, between views and view controllers, which handles data transformation.

The basic structure of the MVVM pattern in iOS

(Yes, I know that this diagram is not accurate. Read on.)

Sounds pretty straightforward, doesn’t it?

Unfortunately, in iOS, the MVVM pattern comes with its own problems.

The shortcoming of a diagram like the one above is that it does not show all the nuances of the interactions between these layers.

This leaves many things open to interpretation.

If you read iOS development articles about MVVM, you will find different ideas surrounding it. And even the most vocal proponents of MVVM cannot agree on some of those.

This article has a good roundup of the disagreements around MVVM.

This is one of the reasons that made me skeptical about MVVM for a long time until I found a more pragmatic approach.

Let’s have a look at these problems.

View models should be value types, not reference types

The first big problem I found in MVVM is having view models as reference types.

This might look like a small detail at first, but it has huge implications.

On this topic, I am in the same camp as Andy Matuschack. He gave a great talk about the difference between value types and reference types which I really recommend.

The idea is that:

  • value types are inert and can have only a single owner. This makes it much easier to reason about their code;
  • reference types behave and can have many owners. They receive method calls from other objects, global notifications, callbacks, Key-Value Observing notifications, etc. This makes it much harder to reason about their code.

This shows how having view models as reference types makes a huge difference. View models become stateful objects that can be updated by different parts of your code. This often leads to unexpected behavior and strange workarounds.

All of a sudden you have a third layer in MVC that behaves on its own.

You can see this clearly in most of the diagrams for MVVM in iOS, which usually take this form:

A wrong diagram for MVVM in iOS

I’m showing you this just for information, but I recommend to not follow this approach.

Here you can clearly see that:

  • the view model is a layer that behaves. It “owns” the model and gets updated by it, by some form of notification or callback;
  • even model types are reference types. That’s the only way they can be able to send signals to view models.
  • it seems to ignore completely the model controller layer of MVC. In fact, view models seem to replace model controllers in this scheme.

Behaving view models have big downsides. For one, you can see that they get at least two responsibilities if not more. They retrieve data and format it before passing it to a view.

In fact, Andy Matuschak himself makes this remark in one of his talks on refactoring mega controllers. At the 45:08 mark he explains how MVVM gets the data conversion part right, but the rest wrong.

Watch that whole talk, it’s a great one which greatly influenced the way I write iOS apps.

So view models as reference types are inherently problematic.

But this is just the tip of the iceberg.

Networking code should not go inside view models

Another problem of view models is that, according to some of the proponents of this version of MVVM, networking code should go inside view models.

Here we see one big effect of having view models as reference types. If they were value types, this would not be possible.

It is understandable that networking code ends in view models when your version of MVVM:

  • does not contemplate the existence of model controllers;
  • explicitly states that view models behave and update view controllers through callbacks or some form of notification.

This makes view controllers and view models the only candidates for networking code.

And since everyone knows view controllers are already too big, networking code ends in view models.

This creates a very intricate network of callbacks between objects that are hard to follow and leads to many bugs.

And we are not even considering unit testing, which is another can of worms.

I already showed that networking code should instead go inside model controllers and that model types should be inert value types.

Are we done with the problems?

No, not yet.

There is another pattern encouraged by this version of MVVM. This to me looks like the Pandora’s box of asynchronous code in iOS: functional reactive programming.

Functional reactive programming frameworks hide a lot of complexity behind the scenes

One thing that often goes hand in hand with this version of MVVM is functional reactive programming or FRP.

FRP is a programming paradigm for asynchronous data flow which uses the building blocks of functional programming like map, reduce and filter.

In iOS, FRP usually comes in different flavors:

When using an FRP framework, your code looks like this:

I originally wrote a detailed analysis of this piece of code, and then realize it was a waste of both my and your time.

I’ll just write a couple of considerations instead.

This code hides a lot of complexity behind the scenes. There are a lot of asynchronous operations and callbacks going on in the background, even in places where asynchronous code is not needed.

In fact, you can clearly see it in this documentation page for RxSwift, which talks about bindings, retries, delegates, KVO, Notifications etc.

The idea behind FRP is that you write “declarative” code and give up the control of the asynchronous operations. This exactly what I don’t like about FRP. Its code is not explicit.

The FRP fans usually reply: “but look how little code I wrote. It just works like magic!”

To me, this is exactly the problem.

In software development, if something looks too clever or looks like magic, it’s probably a bad idea.

This approach makes it very easy to write code. But:

Programs must be written for people to read, and only incidentally for machines to execute. – Harold Abelson

This is a matter of definition. FRP proponents will tell you that this code is exactly that: perfectly readable.

To me, it’s not. It reaches such high levels of abstraction that it’s very hard to know what it does exactly.

Also, try to debug problems in a couple of months, when you won’t have any idea about how all this asynchronous code works. As Brian Kernighan once said:

Everyone knows that debugging is twice as hard as writing a program in the first place. So if you are as clever as you can be when you write it, how will you ever debug it?

The point is the same as discussed above. This creates a complex network of objects that behave all in their own way, which makes it very hard to reason about this code.

Sure, you can write it very fast. But code is written once and read many times. You are going to pay down the line for this time saving.

And finally, everybody in your team needs to learn a whole new paradigm. That piece of code sure is short, but RxSwift documentation definitely isn’t.

One last problem is that FRP is a paradigm, and its implementations differ.

Look at the answer to this question on RxSwift and ReactiveCocoa on Stack Overflow. It states at the very beginning: “Comparing the two worlds is very hard”. Later, the conclusion is: “RAC and RxSwift are 2 completely different beasts”.

So these paradigms are not interchangeable. Even if you know one FRP framework, you will likely have to relearn everything when you work with another one.

In the end, the choice is yours. In my opinion, it creates more problems than it solves.

If you like this kind of magical programming, go for it. But you probably won’t like most of the stuff I write in my material.

If instead, you want to follow a different approach to MVC and MVVM, read on.

View models as inert value types

If we remove make our view models value types, all that I have illustrated above is not possible anymore.

No more state, networking and FRP in view models.

Which is good.

The view model layer of this take on the MVVM pattern becomes inert. As such, it cannot be a connecting layer between the view controllers and the model.

View models become instead a helping layer that sits beside view controllers and views instead of between them.

And the only responsibility of view models becomes transforming passing data to views in the correct format. The connections of MVC as we know it are respected:

A pragmatic approach to MVVM in iOS

You can see that there are no arrows going out of view models. That’s because, as the name states, they are model objects. And model entities are inert value types.

But unlike the model types of our app, which represent data and business logic, view models represent visual information.

This allows us to:

  • take the responsibility of data formatting out of view controllers
  • define simple view types to pass data to views
  • make transformation code and custom views reusable across our app and even across different projects
  • extend view models so that the same custom view can visualize different types of data

As usual, let see a practical application of all this theory.

We will build a simple app that displays flight information.

This will allow me to show the clear separation between model types, view models, and transformation code.

Design of the MVVM example app

A fundamental block of MVVM are custom views

Let’s start with the simple stuff.

First of all, we create the interface for our view controller, in interface builder.

UI for the MVVM example app in Interface Builder

Here I created the card following the design above, using a combination of simple views and labels.

I placed these views using a combination of stack views and Auto Layout constraints. I also set some User Defined Runtime Attributes for the shadow around the card. You can see those values on the right in the image.

But this is not the important stuff.

Since this is not an article on Auto Layout and UIs, I am not going over the details. You can find them in the Xcode project on Github, and a longer discussion in my free course on making professional iOS apps.

The important point is that, according to our version of MVVM, this view needs to have a custom class, which hides its internal implementation.

As you can see, the FlightCardView references its own subviews, which in this case are all labels, through outlets.

It is common to see outlets only go from a view controller to all the interface elements in its view. But views can have outlets to their own subviews too.

This is a fundamental part of MVVM.

When a view controller has outlets to all the interface elements in its view, it means that it knows too much about the internal implementation of such view. So it is violating the guidelines we laid out above.

With custom views instead, a view controller does not need many outlets.

In this example, our view controller actually has only one outlet.

Views do not need to respect the structure of model types

We will now move to the other end of MVC and create model types for our app.

If you look at the code for our custom view above, you will see that it has a flat structure. All that class has are outlets to labels.

This does not mean that our model types need to have the same flat structure. Nor it means that we will need to update our view if we end up with model types that don’t reflect its flat structure.

This concept is at the core of MVC and MVVM.

Views and model types are completely disjointed. The former are concerned only about how information is presented to the user. The latter, only how data is represented internally.

They don’t need to know anything about each other, nor they need to reflect each other in any way. They might even represent information in completely different ways.

In MVC it is the responsibility of view controllers to bridge this gap. In MVVM it will be the responsibility of view models.

So let’s start to think about the data structures we need for our flight information.

A flight connects airports, of which there are many around the world. So we need an Airport structure.

Here I just created the properties we need our example. Of course, you can add other ones and make some of them even have their own custom type.

Flights are not only about airports. A specific flight leaves from or arrives at an airport at a specific time. We can call these endpoints, of which a flight has two: the departure and the arrival.

So we declare a structure to identify endpoints:

Finally, a flight is operated by an airline and has a number that identifies it.

There is one last property we want if we look at the design for our small app.

A flight has a duration. Regardless of the fact that this is something we want to display on the screen, the duration is a property of a flight, so it belongs to the model. In the future, we might, for example, use the duration to sort flights.

It is important to notice two things:

  • the structure of these model types is different from the structure of the view. While the view is flat, our model types are organized in a hierarchy (see the diagram below)
  • the data representation also differs. While our view displays everything in the form of formatted strings, in our model types we have Date and TimeInterval types. Data representation and data visualization are separate. The model uses the best types for the representation, regardless of what a view might need to visualize.

The data model layer of the app

View models provide data to a view in the final format

Let’s now bridge the gap between model types and views using view models.

As I explained above, view models are simple types used to pass data to a view. As such, they need to provide the data to a view in the format that the view needs.

Our view needs strings for its labels, nothing else.

So that’s exactly what our view model needs to provide:

As you can see our view model is a struct, so it’s an inert value type:

  • it does not behave,
  • it does not access the model by itself,
  • it does not receive callbacks and notifications,
  • it does not make network calls, and
  • it does not update views.

These responsibilities remain inside model controllers and view controllers.

Also, all the properties of our view model are of type String. This is important.

A view is concerned only with data visualization, not with data conversion. It’s not a responsibility of a view to assemble strings or to format data.

Our FlightCardView needs data already in the final format that fits into its labels.

This ViewModel is part of the FlightCardView class. As such, it is a subtype of FlightCardView. I usually put these types in separate extensions for better code organization. You can also declare them directly inside FlightCardView. It’s just a matter of style.

Either way, the fully scoped name of this view model is FlightCardView.ViewModel. This makes it clear that this struct exists with the sole purpose of passing data to FlightCardView.

Transforming data from model types into view model formats

Now we need to write the code that converts our model types into a ViewModel value.

This code belongs to ViewModel itself. That is exactly its purpose.

Sometimes though we want to make some code reusable across different view models.

For example, there might be other views in our app that display dates. This means that we will need more view models that convert a Date into a String with a specific format. It is usually common to use the same data formatting across all the screens of an app.

So we can put such code inside a Date extension.

Here we extract both the day and the time from a Date since in our design we need both separately.

In the same way, a duration also has its own format. We need to convert a TimeInterval, which is expressed in seconds, into a string like 3 hours, 10 minutes

In our design, we also see that the time and the airport information are grouped together on the same line. So we need code to assemble time, city and airport name into a single string:

I also put this code into an Endpoint extension, but where you place it actually depends on the use you make of it.

Here I am assuming that we will need it somewhere else in our app. If you need this code only in a view model, then it’s better to place it inside that specific view model.

Keep in mind that these extensions are part of our data formatting code. So, even if we are extending our Endpoint structure, this code is not part of the model layer.

This code belongs to the view model layer. As such, only view models should use it. Using these extensions in model types is a mistake.

If you want to enforce this restriction more formally, you can declare them to be fileprivate, so they won’t be visible in other parts of your app. This is not always possible though unless you put a lot of view models in the same file.

We can finally write the full code that converts a Flight into a ViewModel:

This code takes a Flight value and converts its data into the various strings the FlightCardView needs.

I also added an initializer to create an empty view model. This is useful to reset the values in the labels of the view. It also helps to remove optionals from our code.

I put these initializers in an extension, so we don’t lose the default member-wise initializer of our ViewModel structure. That might be useful for unit testing.

All we need now is a way to pass a ViewModel to a FlightCardView

We can do so through a property with an observer that updates the labels every time a new view model is assigned.

In the future, we can also use this view to display data other than our Flight structure. All we need to do is add a new initializer to ViewModel.

No other code would change. Not the FlightCardView, nor the Flight structure and not even the ViewModel structure.

View controllers are responsible for the flow of data from the model into views

We now need to put together our app to display some information on the screen.

As MVC dictates, data flow passes through view controllers.

First of all, we need some actual data to display:

Be aware that I put the creation of this data in the view controller for convenience in this example, but this is not the correct place. Data should come from a model controller, which reads data from files on the disk (for example, using property lists) or through a network request.

All the view controller needs to do now is initialize a ViewModel and pass it to its FlightCardView

Notice that, of all the code we wrote, only 2 lines belong to the view controller.

Quite a difference from the massive view controllers you usually see in an iOS app.

You can find the whole code for this example on GitHub

The advantages of inert view models

As you can see the advantages of having inert view models are many:

  • we respect the structure and the roles of MVC;
  • views and data are completely disjointed;
  • there is no access to the internal implementation of a specific view;
  • view controllers have a reduced role and less code;
  • views become reusable across screen and even across projects;
  • extending view models helps reuse the same view for completely different types of data;
  • view model code is easily testable without needing test doubles like mocks

With this apprach to MVVM we don’t solve all the architectural problems of MVC, though.

I dedicated a full article to advanced iOS architecture to those. There I cover the other problems of the MVC, MVVM and VIPER patterns.

The solution to all those problems is my new Lotus MVC Pattern, which I also present in that article.

The Lotus MVC Pattern also includes the approach to MVVM I exposed in this article, so the two articles are complimentary. That article is a bit too advanced though, so beware.

I recommend starting from a solid understanding of this approach to MVVM. And finally, patterns like MVC and MVVM are important also for SwiftUI apps. You can find more about it in my free guide below.

Architecting SwiftUI apps with MVC and MVVM

It’s easy to make an app by throwing some code together. But without best practices and robust architecture, you soon end up with unmanageable spaghetti code. In this guide I'll show you how to properly structure SwiftUI apps.