Before your next project, learn MVVM
I am not an expert in iOS development. I recently started to develop applications for multiple industries in Swift after learning for a month. I did like every beginner and opened Apple documentation and followed their guidelines. The recommended pattern by Apple is the MVC model. I started building my first apps following the classic principles of Model View Controller that we all know and cherish. On paper, the MVC model in iOS development is no different than the one we used for years. Unfortunately for me, I always were interested in Software architectures, clean codes and beautiful pieces of software and as such the combination of iOS and MVC were a pain to work with.
The Massive View Controller 😓
After a few weeks, with applications gaining in complexity, both visually and behind the scenes, the problem was apparent. When developing for iOS, your view, also called Storyboard, is a simple XML file with all the different component and their coordinates and constraint. The result of that is that your
ViewController files in charge of instantiating and managing those views get cluttered and gigantic compared to their actual functionalities. You start to view different concerns mixing in the same classes: Routing, Model handling, animation, view population... are all together 💀. This confusing and painful to work with and often referred as the Massive View Controller syndrome. So went looking for other options, and here entered MVVM.
Model View ViewModel 🚀
The MVVM pattern, or Model View ViewModel, revolve around the idea that Model handling and View handling should be separated. This allow better testability, readability and ease of evolution. To achieve that, we will split our
ViewController in two. The
View (which still inherit from the Apple
ViewController class) will populate the views and manage animations. Everything displays in the screen fall under its control. The
ViewModel in charge of interacting with APIs, databases and other sources of informations and extract the informations related to its corresponding view. When the view is instantiated, the
View binds itself to its
ViewModel and will update itself according to the data provided from the
ViewModel. There are multiple ways to achieve that, but my preferred method offered by Swift is through closures.
To illustrate this, let’s imagine a stupid view that print the current date. We can start by designing the
ViewModel exposes one variable and one method. The
fetchDate() method will fetch the current date and assign it a private variable. This variable will call an optional public method
updateDate each time it is set. This method is a public variable and a such can be set by the
After instantiating the
View will simply attach its closure(s) to the exposed variable of its
ViewModel and then trigger the fetch. The setupView method will populate the view with initial content if you require it. When the fetch complete with success and the callback is called on the
updateDateClosure will be called and will update the label. All of this asynchronously and easily maintainable.
The routing situation 🚥
The only thing that was still annoying me was that the
View still had to handle routing through
prepareForSegue and other methods. After achieving such a clean separation between presentation and model handling, we cannot simply be happy to let routing in this situation. To tackle that issue, I created a simple Router protocol (the Swift equivalent of the old Interfaces):
init method is a convenience init to not have to look for the corresponding
Storyboard. The Enum and Protocol are just here for clarity purposes. But this allow us to simply reference this router in our files:
And we can the easily navigate through pages with
This pattern is pretty simple, quick to implement and easy to learn. It still has some traps that you need to be aware of:
- The closure and asynchronous nature of the pattern makes it harder to debug. The stack quickly become cluttered by the calls to each method.
- Be aware of the strong references to avoid memory leaks in closures and class variables. For example, if you forget the
weakkeyword in the router reference to the view, they will each reference each other with a strong reference and will never be deallocated from memory.
I cannot overstate how much a difference a good architecture can do. With a good architecture, implementing features become easy and fast. Whatever you favorite architecture pattern, before your next project, give a try to MVVM 👍. If you are interested in the subject, you can also read about Viper and MVVMC