As Swift grew in popularity in recent years, so has MVVM - with many talks and tutorials singing its praises. As a community, we love to talk about design patterns but we should get better at understanding the problems, not focusing on the concrete solutions. MVVM defines some principles and leaves a lot of uncertainty when applied to real-world iOS cases. In this talk, we will see how these work in practice and step away from the reactive dogma to focus on the roles defined by the pattern.

The Big Fuss

Let’s dive into this very interesting topic, MVVM at scale.

I recently read a blog post that started like this: “MVVM has been a trendy topic lately.” I can’t agree more with that. During the past year, I kept hearing about how it became one of the most used iOS architectures. If you feel late to the show because you don’t know what MVVM is or have never used it, I have to tell you a secret: we’re all late. It turns out that this blog post was written by a .NET developer in 2009.

I’ve known about the pattern for a couple of years, and I use it here and there, but never changed the way I design applications that much. I didn’t feel that strongly about it, so I was really curious to know why the iOS community loves it so much and how they apply it.

I started reading about it and watched some talks. I was a little bit confused at some point. I had more questions than answers but that’s fine, because those questions led to more research. It led to a lot of interesting discussions with colleagues, and that’s why I’m here today - to share some of the thoughts that I have and hopefully inspire you to think about designing applications from a different perspective.

Quick Intro to MVVM

MVVM defines three roles an object can play. It can be either a view, a viewModel, or a model. I intentionally put view controllers on the diagram because, as with other universal patterns, sometimes it’s not clear where certain iOS objects belong.

The key thing here is the viewModel. It keeps the view state and it exposes some mechanism for providing updates. This can be achieved through using key-value observing, property observers, exposing RX observables, whatever you choose, like closures.

And it doesn’t really matter what you use. Some techniques make it easier to do this - like reactive programming - but it’s not really a requirement of MVVM.

Motivation for Using MVVM

Why would you want to know about this pattern? When I watch talks and read blog posts, the most interesting thing is the motivation that the author has to share his or her thoughts. When it comes to MVVM, there’s a quite stable pattern I see. It’s almost always about view controllers in MVC doing too many things and thus being not testable, and how with MVVM you can address those two problems. I’m personally a big fan of making things more simple and testable.

Examples of MVVM Common Uses

Let’s take a look at some of the commonly used examples that you’ve probably seen around.

Example 1: Presentation Logic

To illustrate them, I will use one view controller that shows a list of the places where a user checked in, like the places he visited for a certain period of time. For this first example, we have a label that presents and describes this time period.

var startDate : Date ? var endDate : Date ? var tripDurationLabel : UILabel func updateTripDurationLabel () { }

Get more development news like this

This label can show different text based on the dates.

var startDate : Date ? var endDate : Date ? var tripDurationLabel : UILabel func updateTripDurationLabel () { var text = "" if let startDate = startDate , let endDate = endDate { text = "All checkins between \( dateFormatter . string ( from : startDate ) ) and \( dateFormatter . string ( from : endDate ) ) " } }

If both dates are set, we have one text. Then, we have different texts if our start date or end date is set.

var startDate : Date ? var endDate : Date ? var tripDurationLabel : UILabel func updateTripDurationLabel () { var text = "" if let startDate = startDate , let endDate = endDate { text = "All checkins between \( dateFormatter . string ( from : startDate ) ) and \( dateFormatter . string ( from : endDate ) ) " } else if let startDate = startDate { text = "All checkins after \( dateFormatter . string ( from : startDate ) ) " } else if let endDate = endDate { text = "All checkins before \( dateFormatter . string ( from : endDate ) ) " } tripDurationLabel . text = text }

That’s quite a lot of logic to keep in the view controller. So, some examples suggest that we can move this type of logic to a viewModel instead.

tripDurationLabel . text = viewModel . tripDurationString

This viewModel will expose only the final string, and we’re done. We can easily test this by not even instantiating the view.

Example 2: Business logic

let startDatePicker : UIDatePicker let endDatePicker : UIDatePicker let startDateTextField : UITextField let endDateTextField : UITextField func endDatePickerValueChanged ( _ datePicker : UIDatePicker ) { }

This is another example that I want to show. Let’s say that the user has the ability to change that time period in the same view controller using some date pickers. Every time a date changes, there are certain validations that need to be performed.

let startDatePicker : UIDatePicker let endDatePicker : UIDatePicker let startDateTextField : UITextField let endDateTextField : UITextField func endDatePickerValueChanged ( _ datePicker : UIDatePicker ) { // validate selected end date // update the end date text label with a formatted date // check if current start date is still valid // update the start date text label if needed // calculate the maximum start date based on the new end date // set the maximum start date to the start date picker }

I added comments here because the code is very long, but basically I am validating the time period, updating the text fields, updating the maximum dates for the date pickers.

func endDatePickerValueChanged ( _ datePicker : UIDatePicker ) { viewModel . updateEndDate ( datePicker . date ) }

We can move this code to a separate object that we only notify about the date change and then let this object perform all those validations. We are moving the business logic into this separate object.

Example 3: Networking & Parsing

func fetchTripCheckins () { }

Finally, there is another type of logic that sometimes creeps into the view controller, and some examples suggest that we can move that also to a viewModel when talking about networking and parsing.

func fetchTripCheckins () { let url = // construct the url let task = URLSession . shared . dataTask ( with : url ) { ( data , response , error ) in if ! error { // parse data response // save current state } reloadTripView () } }

Instead of the view controller knowing how to get its data and how to parse that data in order to show it, we can again move it to a separate object and just observe the changes in that object and reload the UI.

private viewModel : TripViewModel { didSet { ( ... ) reloadTripView () ( ... ) } }

If we follow those three examples, we’ll end up with a very light view controller that can be tested without even interacting with UIkit.

Lighter View Controllers, Fatter viewModels

There is another thing, if we moved all of those three things to one single object, it won’t be very light. Hiding complexity is not necessary - it’s not removing it overall. It’s definitely good at a higher level, but working with a fat viewModel could be as fun as working with a big view controller.

When I see code like this, I ask myself “Why? Is that how MVC really makes us write code?” I think that the fact that we had that logic in the view controller in the first place is not really a requirement of MVC.

This tweet sums it up really well. It’s basically about a blog post for refactoring a big view controller that does a lot of things like navigation, data formatting, networking - all the things that we already saw.

When you start with something like that, any solution that you present will look attractive, right? It will be definitely better than it was before…

You can replace MVVM with any generic pattern and now you can cover most of the topics about design complications.

Some examples are so catchy that you can easily get into this mindset that you should be using it for everything. We all know that feeling of “I’ve been programming wrong, I have to start using this today!”

But that’s not the way I think design solutions should be promoted. I think that we should focus on the root causes of the problems and not trying to effect only their symptoms. By doing this we write good software - by understanding the concepts and making informed decisions, rather than through encouraging something that I call “Community Pressure-Driven-Development.”

When we think about MVVM, when trying to learn something different where MVVM is not necessary doesn’t really help. Some examples are good, but they should be focused. It should be clear what the example is showing. Taking some design shortcuts when you present a new API, like putting everything in the view controller, maybe it’s fine if that’s the goal of your demo, to show the new API and not explain how to use MVVM.

Demo: My Use of MVVM

You might be wondering if I actually use MVVM. Yes, I do. I want to show you a small application that I started writing for myself recently. I tried to keep it tidy and familiar. I tried to use concepts that I use in my work. The app is called TripCheckins, and it has views, models, viewModels, all that.

This is how my project structure looks. I used Foursquare to get the Checkins, and my project structure already introduced adding and previewing Trips. I think that’s way more important than having your project structure implying some design pattern usage. I say “implying” because putting something in the view folder doesn’t really mean that it will not contain business logic. Naming something a viewModel doesn’t guarantee that it won’t have UI code.

When you’re building an application, the design patterns that you use may change, but the intent of the code, most of the time, will stay the same. Try to make it clear for people what the intent of the app is, and don’t assume that they will know certain concepts.

You actually saw some of the code for my app already during the examples. It has this screen for routing a trip where I have the date pickers for selecting a time period.

There are some rules, like resetting the start date when you choose earlier end date, and restricting the start date when end date is set, and there are some other rules as well.

protocol DateFilterCreationViewModel : DateFilterProvider { var maximumStartDate : Date { get } var maximumEndDate : Date { get } var startDateString : String ? { get } var endDateString : String ? { get } mutating func updateStartDate ( _ startDate : Date ?) mutating func updateEndDate ( _ endDate : Date ?) }

I decided to use a viewModel for this view for a couple of reasons, but this is what the viewModel looks like. The reasons are that I didn’t want to keep all those rules in my head. I wanted to document them in tests. That’s really easy with MVVM. When a date changes, the whole view basically needs to update. I thought observing a viewModel seemed like a logical solution.

I wanted to change the native date pickers at some point to create my custom views when I have time. And I think that using this same interface can work with both UIs. That’s why I decided to use MVVM for this view.

It was really easy to use. I captured all the rules and tests without even creating the view.

Tidy View Controllers

The view controller also was really tidy. The code here is small, but basically the view controller fits on one screen.

class AddTripViewController : UIViewController { weak var delegate : AddTripViewControllerDelegate ? let dateFilterCreationView : UIView & DateFilterProvider init ( dateFilterCreationView : UIView & DateFilterProvider ) { self . dateFilterCreationView = dateFilterCreationView super . init ( nibName : nil , bundle : nil ) } override func viewDidLoad () { super . viewDidLoad () view . backgroundColor = UIColor . tintColor () title = "Add trip" navigationItem . rightBarButtonItem = UIBarButtonItem ( barButtonSystemItem : . done , target : self , action : #selector( doneButtonTapped(_ :) ) ) navigationItem . leftBarButtonItem = UIBarButtonItem ( barButtonSystemItem : . cancel , target : self , action : #selector( cancelButtonTapped(_ :) ) ) view . addSubview ( dateFilterCreationView ) } override func viewDidLayoutSubviews () { super . viewDidLayoutSubviews () dateFilterCreationView . frame = CGRect ( x : 0 , y : 100 , width : view . frame . width , height : 50 ) } }

It does initialization, it adds the date filter creation view, does the layout, and captures the actions.

That’s the whole view controller!

But, I would actually have similar logic if I was using MVC in my view controller. I’m actually cheating a little bit here, because the nested view that I have is using MVVM, but the outer view doesn’t know anything about the viewModel. I would say that it’s super similar to MVC.

Check-in List

Let’s take a look at another example that has more logic in the view controller – This screen shows the check-ins the user has, with:

List of check-ins

Dynamic title

Loading state

Error state

Reload and Add actions

struct CheckinItem { let venueName : String let city : String ? let country : String ? let date : Date let dateTimeZoneOffset : Int }

The CheckinItem is how I represent the information that I get from Foursquare. It’s a simple struct.

struct CheckinListItemViewModel { let venueName : String let locationName : String let dateString : String private static let dateFormatter : DateFormatter = { let formatter = DateFormatter () formatter . dateFormat = "MMM d 'at' h:mm a" return formatter }() init ( checkinItem : CheckinItem ) { self . venueName = checkinItem . venueName self . locationName = checkinItem . city ?? checkinItem . country ?? "" let dateFormatter = CheckinListItemViewModel . dateFormatter dateFormatter . timeZone = TimeZone ( secondsFromGMT : checkinItem . dateTimeZoneOffset ) self . dateString = dateFormatter . string ( from : checkinItem . date ) } }

This is the way I present it to the user - it is the viewModel that has date formatting. It constructs the location string, and it can have different logic, basically. It’s a simple immutable struct.

struct CheckinListViewModel { let title : String let listItemViewsType : CheckinListItemViewsType let state : ListViewModelState } enum CheckinListItemViewsType { case compact case normal } enum ListViewModelState { case loadingItems case error ( String ) case loadedListItemViewModels ([ CheckinListItemViewModel ]) }

I have this other viewModel class that’s just for the whole view that you saw in the previous screenshot. It has information about the title, the state, and the current view preference.

It’s basically a viewModel that is for the outer view that keeps reference to the nested viewModels. We have nesting here.

I think that we should extract viewModel logic the same way we extract subviews that can be reused together into their own classes.

In that way, not only can we can reuse the viewModels, but we can also change the way they behave without thinking about the whole system and touching a lot of code.

Creating viewModels

protocol CheckinListController { var currentListViewModel : CheckinListViewModel ? { get } var onViewModelUpdate : (() -> ())? { set get } func reloadListItems () }

Everything so far was immutable structs. If someone needs to keep track of the state of the current viewModel. In my case, this is another object. It knows the business rules, and it has some dates. It’s also part of the viewModel there, in my application. I didn’t follow the same name convention because I just think it makes more sense like this.

The important thing about this subject is that it knows how to get the check-ins. It doesn’t really perform networking and parsing. I think this is part of the model layer. The viewModel should know how to interact with the view model layer, but they should not contain that type of logic.

class CheckinListViewController : UITableViewController { ( ... ) init ( controller : CheckinListController ) { self . controller = controller super . init ( nibName : nil , bundle : nil ) self . controller . onViewModelUpdate = { [ weak self ] in guard let listViewModel = self ? . controller . currentListViewModel else { return } self ? . configureWithViewModel ( listViewModel ) } } }

My view controller had only logic for keeping in sync with this viewModel. I injected the viewModel. This CheckinListViewController gets injected in the view controller – that is my own preference. I like to keep the viewModels injected or created by other viewModels because this makes it easier to reuse even the view controllers. In fact, in this sample application that I am showing to you, I’m using the CheckinViewController for two cases - for showing a trip and for showing a feed without check-ins. It’s good - we can test it.

What to Consider with viewModels

private var listViewModels : [ CheckinListItemViewModel ]? { didSet { self . tableView . reloadData () } }

When you use a similar approach, you have to think about two things. One is, if you have a viewModel that may change super quickly, you may not want to reload the whole view every time. For example, if you have some logic that is location-based or playback-based, you may want to have some diffing logic or send more granular updates. The other thing is, if you’re using a lot of value types, just remember that when they’re passed around they’re copied. Be mindful about memory usage.

class AppCoordinator { ( ... ) init ( navigationController : UINavigationController , authorizationTokenKeeper : AuthorizationTokenKeeper ? = nil ) { self . navigationController = navigationController self . authorizationTokenKeeper = authorizationTokenKeeper if let token = authorizationTokenKeeper ? . authorizationToken () { showCheckinsList ( authorizationToken : token ) } else { showAuthorizationViewController () } } private func showCheckinsList ( authorizationToken token : String ) { let checkinsService = FoursquareCheckinService ( authorizationToken : token ) let controller = AllCheckinsListController ( checkinsService : checkinsService ) let viewController = CheckinListViewController ( controller : controller ) viewController . delegate = self pushViewController ( viewController ) } ( ... ) }

One thing that I didn’t mention so far was how the navigation in this application happens. I left it for last because it’s not really MVVM-related. Here in this app I used a coordinator, but in my previous projects I used routing via identifiers, or via URLs – doesn’t really matter.

extension AppCoordinator : CheckinListViewControllerDelegate { func listViewControllerDidTriggerAddAction ( _ controller : CheckinListViewController ) { let viewModel = AddTripDateFilterViewModel () let dateFilterCreationView = DateFilterCreationView ( viewModel : viewModel ) let viewController = AddTripViewController ( dateFilterCreationView : dateFilterCreationView ) let addTripNavigationController = UINavigationController ( rootViewController : viewController ) viewController . delegate = self navigationController . viewControllers . last ? . present ( addTripNavigationController , animated : true , completion : nil ) } }

Whatever you choose, just try to make the flow logic easy to follow.

MVVM vs. MVC

What I’m trying to say is that I’m trying to be pragmatic when I apply MVVM and that I combine it with other design patterns, too.

I like to use separate objects that I consider part of this viewModel layer, because I think about the components of design patterns that specify layers of objects, not single objects that know everything.

When I compare this to MVC, I think that it’s not that different. I actually can’t relate much to the motivation that I shared with you in the beginning. It may be controversial to say, but I don’t think MVC is that bad.

The best thing that MVVM does is that it forces you to create this additional object. It doesn’t feel good to call the view controller a viewModel, right? Sometimes it might be not necessary and will just force you to write this additional code.

Managing Complexity

That’s why programming is so challenging, because at the end of the day, when it comes to technical solutions, complexity is the thing that matters the most.

“Managing Complexity is the most important techical topic in software development.” -Steve McConnell in Code Complete

I really like this quote. Reducing the size of the pieces of the software that you keep in your head is actually leading to this testability. It makes testing easier, and it’s just a side effect.

Don’t forget that when applying design patterns, they won’t fix all of your problems. Just think about making things simple and keep in mind the design principles. You can still have a very big and complex viewModel that does a lot of things and has duplicated logic in MVVM. You can have this tidy view controller that delegates tasks to other objects that have single responsibilities. With MVC, it doesn’t really matter what design pattern you use.

I think that’s what’s important when it comes to skill. If you follow these principles that were around for years, you will be able to break a big system into smaller systems and focus on familiar problems, on smaller problems.

I think the way of doing good promoting is through giving more real-world examples and through focusing on the root causes of the problems and presenting some potential solutions, helping people to learn and to make informed decisions. Thank you.