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 is confusing and painful to work with and often referred as the Massive View Controller syndrome. So I 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
:
Here, 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 View
:
After instantiating the ViewModel
, 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 fetchDateFromApi
function, 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):
The viewController 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 route?.navigate(.home)
.
Caveats ⚠️
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
weak
keyword 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