I’ve seen many developers and articles blaming the Model-View-Controller architecture for many, if not all, of the troubles that are brought up in the iOS community.
Today, I offer a few alternate views and approaches to this issue. Moreover, we also explore a few techniques/guidelines that can reshape the concepts of MVC and project management in general.
You’ve probably seen this diagram from Apple’s official documentation:
MVC seems pristine in theory, just like the diagram shows.
The view allows users to interact with it, and user actions are captured using the controller, which then updates the view accordingly. If a user action requires an update to the model, the controller waits for the ‘update completed’ notification from the model, after which the view is updated.
It seems simple enough, and everything should work as intended, right? Hundreds of thousands of applications have been written using this approach, even large ones. Not only iOS and macOS apps, but Web applications, Windows applications, and others have also been using this approach for nearly four decades.
One of the most touted advantages of MVC, especially within iOS, is the ease of use. UIViewController and its successors in the UIKit framework provide many functionality and opportunities. You don’t even need any programming skills to write smaller apps. Anyone can have an app up and running after following a few YouTube lectures for a few days.
Many applications still use this approach today and don’t seem to run into any issues. So, where does it all go wrong?
Problems begin cropping up when you’re working on relatively larger projects. Evidence and experience show that MVC scales poorly, leading to a critical and well-known issue called the Massive View Controller.
The Core of the problem
I see two core reasons for this issue:
- MVC doesn’t give clear instructions on what entities and classes you need to create and which ones you don’t — especially in iOS. UIKit MVC initially only gives you the UIViewController, and that’s it. The view exists but is not declared initially.
Hence, the application will perform its functions even when you don’t create a separate view instance and have only the UIViewController. It’s the same with the model. The structure and architecture of the model, as well as its interaction with the controller, rely entirely on the creativity and imagination of developers.
This forms the basis for the second reason.
- Due to poor understanding of the domain area and the inability of developers to allocate necessary entities, we often see haphazard accumulation of dependencies and functionalities within the UIViewController.
Here, when the developer adds new functionality, instead of creating new entities and refactoring the existing architecture, it is added to the ViewController. Hence, we can safely blame the lack of proper architecture and inattention/negligence/ignorance/inexperience of developers for the issues we see in MVC.
Tackling the First Problem
The approaches of MVVM, MVP, VIP, VIPER, Flux (Facebook), Riblets (Viper from Uber), Clean-Swift, and others, partially or entirely solve the first part of the MVC problem — making them quite popular over the past few years.
Many give developers clear instructions about the classes and details cases when it is necessary to create them. They also make it much easier to work in a team, mainly if the group consists of 10+ developers who work on the same project (for example, Uber works 150, JustEats 20+, and Facebook). It means creating many isolated objects to cover the Controller code, allowing you to allocate work within the team without losing time.
Handling the Second Problem
I have seen many projects using VIP and MVVM with view-models and presenters classes containing more than 1500 lines of code — featuring multiple operations, from parsing and mapping to querying databases and making HTTP requests.
In general, the problem of developer inexperience or lack of understanding of the application architecture is not solved by simply changing the architectural pattern. In my experience, no “golden bullet” will allow you to maintain/write large and complex projects “by default.” It will always require effort, a unit of tests, thoughtful planning, and time.
What can we do?
1. Update the ViewController
First, no application object should be responsible for more than one function (Single Responsibility).
For many developers, this might be obvious, but many others still don’t adhere to this simple guideline. For many, it’s not apparent that UIViewController is the same object as the others and should be accorded only one responsibility – i.e., linking model and view. It should support the idea in the corresponding model state and notify the model when it needs to change this state. Anything that goes beyond this definition, in my experience, does not belong to the controller.
All other fluff like animations, layout, composition view, changing drawings, parsing, mapping, HTTP requests, databases queries, different operating system services levels, etc., do not belong to the controller. So, avoid creating or calling any of these within the controller.
Another standalone item should be navigation. If your ViewController has more than one exit point, you should consider those points and navigation control. We have often encountered ViewControllers with 5-10 exit points scattered throughout the controller code.
There are many ways of organizing this — including breaking down the navigation into individual extensions, managing navigation exclusively through segue, creating a separate Router entity, inheriting the UINavigationController, and so on.
Just keep in mind what the ViewController should do and what it shouldn’t.
The same can be said about the entities used in MVVM and VIPER (the list is much longer). If it’s a View Model, it’s only a representation of the data for the View. On the other hand, if it’s an entity, then storing and converting data and so on. Before you add a new method or property to an existing class, always consider whether it truly belongs in that class.
2. Model Isolation
One of the most common errors I see in iOS is insufficient isolation of the model level. I find it best to keep models in different frameworks.
For example, if you have a food delivery application, you can render restaurants and other related items (search for restaurants, menus, location, dishes, delivery areas, etc.) in different frameworks. Similarly, for a flight booking application, you can allocate a dozen frameworks (search for tickets, reservations, purchases, history, etc.). A separate unit test suite can then cover each framework.
Of course, you can choose not to do this, but even in that case, you should try writing models that are entirely isolated from any interaction with the controllers.
3. Avoid Massive UIViewControllers
I have seen multiple storyboards in addition to huge UIViewControllers during the course of my work. They need not always be huge in terms of the code length, but sometimes it’s just about the number of UI elements they house.
Developers should ask themselves a simple question when encountering a massive list of ViewControllers: “Can I somehow break this VC into smaller Views or VCs?”
Handpick elements on a screen that perform a logical operation or feature for the user in separate ViewControllers. This next screen is an excellent example.
Here, each cell in the table is a separate VC. The title with the Author’s avatar, author name, and time is a separate ViewController.
The content itself and the link are in a separate ViewController.
Finally, the buttons ‘Like,’ ‘Comment,’ and ‘Share’ are housed in another ViewController.
4. Avoid using a lot of UI code
Using lengthy code to implement simple UI components through the ViewController will inevitably lead to a clunky architecture that won’t scale past a certain point. We can solve this by ensuring the default inheritance of UI components instead of customizing them within the ViewController. Alternatively, you can also add extensions to define the UI elements.
5. Avoid Massive Storyboards
I’ve encountered massive Storyboard files in many projects over the years. They’ll typically contain 20-50 ViewControllers with varying levels of detail.
These projects almost always start smoothly. There’s no hiccup, and everything seems perfect as the completed application chugs. However, this calm only lasts until project requirements change or additional developers are added into the mix. Once this happens, it’s complete chaos, thanks to the complexities involved in figuring out the massive storyboard.
One solution involves not using the Xcode UI Builder and creating the entire layout using code. You can use various libraries to simplify writing constraints (Parus, Masonry, etc.) or write everything yourself.
However, if you must use Storyboard, then try and break your project into several storyboards. A Storyboard should ideally have a single responsibility or use case. For example, if you have a Cab hailing application — the individual Storyboards can be Hire Cab, Tracking, Payment, General Information, and so on.
For those less independent features with storyboard reference, doing this is very convenient and requires a minimum of code writing. Limit the number of screens in a single storyboard to three (3).
The larger the storyboard becomes, the more resources are needed to run it smoothly. In addition to making it harder to figure out and break down, it makes for slow downloading times. Moreover, any newer development teams will have a tough time figuring out a giant storyboard, further slowing development.
The point of this post was to address the tendency to accuse MVC of so many developer issues (especially iOS developers) when it simply isn’t the root of all problems.
More often than not, developers will face similar issues even when moving to other architectures. Of course, those issues might take longer to crop up on different architectures, but they will show up sooner or later.
The Massive View Controller problem crops up in 90% of problematic cases, and this isn’t because MVC can’t scale. But it’s because the original developer(s) didn’t follow guidelines that would help MVC scale smoothly.
Although VIPER, MVVM (and others) can help you get around many of the issues associated with MVC, they’re mostly not the solution for all problems you might face. Some of them can be related to not-so-optimal development practices.
I’ve cited some examples and situations where MVC might not be to blame. Nonetheless, I’m playing devil’s advocate here as I use VIPER/MVVM on my projects.