The Problems with MVVM on iOS
And some ideas for making it better
I love all the discussions I’ve been hearing about MVVM on iOS lately (that’s the “Model, View, View Model” design pattern for those who are unfamiliar). And I’ve now seen four different implementations of the pattern in working apps, one of which I have been involved with myself. But, despite the positive direction MVVM encourages, I’ve concluded that at least most implementations on iOS are plagued by problems that erase much of the intended benefits and end up not being the golden solution they are portrayed as.
I’m not going to cover what MVVM is, or examples of how to implement it here. There are already a few good articles covering that stuff, like this one. Instead, I’m going to cover some flaws and pitfalls to developers who are using MVVM now or have in the past.
Flaw Number One: Moving the Junk to a Different Drawer
One of the goals of MVVM specifically on iOS is to clean up the mess of responsibilities that are usually dumped into the view controller. In addition to configuring / displaying the view and handling user interactions, view controllers typically load data from different sources, filter that data, transform it and format it for use in labels, image views, etc.
This gives iOS view controllers a well-deserved reputation for being “junk drawers”, “god classes” or “bloated classes”.
View models attempt to solve this by peeling off all the stuff related to getting data, filtering data, transforming data and formatting data into a separate class outside of the view controller. While that makes the view controller look better, I have yet to see one of these view model classes in the wild implemented with good single responsibility considerations. Instead, they are like scooping a bunch of junk out of one drawer in your kitchen, and moving it into the drawer below and saying you’ve “organized the junk drawer”.
Specifically, I often see view models doing ALL of the following in a single class:
- Loading data from multiple APIs / endpoints
- Filtering data sets bad on some criteria
- Creating formatted strings composed from several other values
- Being data sources and delegates for UITableViews / UICollectionViews
- Saving or persisting data
That’s still a good country mile from single responsibility classes!
Flaw Number Two: Poor Reusability
Not surprisingly, given the amount of different things happening in a single view model class, these view model are very frequently not reusable between different view controllers. Because they are so involved in formatting specific labels, being a delegate or data source to a specific table view, etc., they don’t work well as view models for other view controllers that have a different style of presenting a table, or a different response to a cell being tapped.
Again, these types of overzealous view model classes have replaced that non-reusability of view controllers with… well, a different non-reusable class.
So, in cases where a view model is doing some work that should be reused (like formatting an address label into two or three lines, by joining the street number, street name, city, state and zip code together), it often can’t be, because the large view model doesn’t fit the new view controller where that address formatting is needed. So, these types of formatting methods are often copied and pasted into multiple view models. This is a true story, one I’ve seen several times in the wild, and something you should review your own code for if you are using MVVM.
Of course, beyond the fact that copy pasta is the worst type of dish to serve in your codebase, it also contributes to the next flaw:
Flaw Number Three: Poor Testability
One of the main intents of MVVM is to improve testability by breaking business logic out of the view hierarchy and into separate classes that can be tested independently from the view state of the application.
This is a great goal, and a key idea. However, when a view model is doing a lot of internal work and holding on to internal state (like loading data from multiple end points, remembering the results, filtering that data into new values that are also remembered, etc.) it becomes very difficult to test all over again. This is because a method like getRecipesForMeal:WithIngredient:
may require methods like getListOfChefs
, getRecipesFromChefs:
, filterRecipes:ByMeal:
to have already run first in a specific order.
So, in order to test the method getRecipesForMeal:WithIngredient:
, you would have to remember to call each of the other methods first, in the right order, and most likely you would also need to set up mock data and responses for getListOfChefs
and getRecipesFromChefs:
as well.
This is the kind of pain that can come from view models — or really any classes — which are trying to do too much.
And lastly, to follow up on the last sentence under Flaw Number Two above: having to copy and paste formatting methods or other business logic into multiple view models also makes testing harder. Because now you need to write and maintain another copy of the test for that method in a second location. If the method / view model were truly reusable, it could be written once, tested once, and used anywhere.
Final Flaw: Without Bindings, It’s Just More Code, So What’s The Point?
The original implementation of MVVM, as part of Microsoft’s Windows Presentation Foundation, made heavy use of data bindings. These bindings saved a lot of needless code by allowing the developer to connect view elements to properties of the data model, which would then ensure that any time the view model changed, the views would be automatically updated.
Compare that to most MVVM implementations I’ve seen on iOS which usually look something like this.
@interface Person : NSObject
@property (nonatomic) NSString *firstName;
@property (nonatomic) NSString *lastName;
@end
@interface ViewModel : NSObject
@property (nonatomic) Person *person;
@property (nonatomic, readonly) NSString *fullName;
@end
@implementation ViewModel
- (NSString *)fullName {
return [NSString stringWithFormat:@"%@ %@",self.person.firstName, self.person.lastName];
}
@end
@interface ViewController ()
@property (nonatomic) ViewModel *viewModel;
@property (nonatomic) IBOutlet UILabel *fullNameLabel;
@end
@implementation ViewController
- (void)viewDidLoad {
self.viewModel = [[ViewModel alloc] init];
self.viewModel.person = somePerson;
[self updateViews];
}
// called when the Person data model changes
- (void)updateViews {
self.fullNameLabel.text = self.viewModel.fullName;
}
@end
Because there are no bindings implemented here, the view controller still has to go through all of its subviews, and set their properties based on values retrieved from the view model.
Without a view model, the view controller would look like this:
@implementation ViewController
- (void)viewDidLoad {
self.person = somePerson;
[self updateViews];
}
// called when the Person data model changes
- (void)updateViews {
self.fullNameLabel.text = [NSString stringWithFormat:@"%@ %@",self.person.firstName, self.person.lastName];
}
@end
That’s actually fewer lines of code, not just in general but within the view controller itself! I’m not saying that this makes it better,but it does imply that even using MVVM, view controllers are still doing much of the same work of retrieving values from a model and setting properties of views to match those values. The fact that the model is now a "view model" doesn't change the fact that the view controller is doing a lot of busywork.
Further, every line of code you need to write and maintain has a potential for bugs and other problems. And a pattern which leads to more lines of code without significant benefit, particularly when there is a way to get the same benefits with less lines of code, should be questioned.
So view models without bindings don’t actually reduce lines of code, they increase them by taking the logic that used to be in the view controller, moving part of the logic to another class, and then still requiring the view controller to ask that new class for the values in all the same places.
With bindings, those values could be tied to user interface elements in the view without the view controller having to explicitly ask for them on every update. This would be a great feature of an MVVM implementation. Unfortunately, I just don’t see bindings in iOS MVVM implementations as a rule.
Perhaps there are some MVVM implementations out there in iOS projects that do use bindings, but they seem to be rare, and the MVVM projects that don’t use them are not even getting one of the major theoretical benefits of MVVM in the first place.
Conclusions
After all that, what should we conclude about MVVM on iOS? Is it worthwhile? Or is it today’s solution that will become tomorrow’s problem?
Well, I can sum up my own conclusions like this:
MVVM is a good architectural idea
It is complicated on iOS by a lack of bindings and by a tendency to continue putting too many responsibilities that were in a bloated view controller class into a bloated view model class instead
Without being broken down into good, reusable, testable, and single-responsibility components, any MVVM implementation is of limited usefulness
Simply saying that “we’re using MVVM” or simply having a rough implementation of MVVM in a project, without taking a thorough look at how it’s improving your code reuse, efficiency and adherence to the single responsibility principle will mislead you as to how valuable or maintainable the architecture really is.
Making MVVM Work Well
So, how can we make MVVM work well on iOS? This would require a couple things:
First, break the different responsibilities of a view controller that get offloaded onto a view model into smaller, reusable, single-responsibility parts. I suggest that these would be Data Sources, Bindings, and Responders which I define as follows:
Data Source: an object that has the single responsibility of retrieving data from any sort of endpoint (REST API, Core Data, file system, etc.) and making it available to other components.
Binding: an object that has the single responsibility of transforming values from one or more data sources into values that are set as properties on a type of target views. For example, a single binding class might take a Person data source, and combine the “firstName”, “middleName” and “lastName” properties from that data source into a single string, which is then set as the “text” property of a UILabel. Any time a data source for a binding has its data change, the binding should automatically update its target views accordingly.
Responder: an object that encapsulates a single action which happens in response to user interaction. At one level, this could be thought of as taking each IBAction that is inside a view controller, and breaking it out into a single reusable class. So if there is an IBAction that saves data when the user taps a button, that would be broken out as a “SaveSomeSortOfDataResponder” class, which could be reused identically across multiple view controllers. Similarly, there could be a “DismissCurrentModalResponder” class that would encapsulate the activity of closing the current modal view controller, and could be reused in many places simply by wiring it up to a bar button item, etc. in a storyboard.
This breaks the idea of a “view model” a little bit, since there is no longer a single view model entity in this pattern, but instead, smaller independent pieces of what traditionally has gone into view model classes.
Second, reduce the amount of code needed to connect up all these single responsibility classes (and reduce boilerplate in general) by designing the needed connections between them to be IBOutlets. This addresses the “more code to maintain” concern, and takes the classic MVVM binding approach a step further, by allowing data sources and responders to also get wired into scenes with simple drag and drop connections.
These two improvements, taken as far as they can go on the iOS platform, would result in an MVVM-inspired approach that is efficient, reusable, testable, and which dramatically reduces the amount of code that needs to be written for your project.
In order to take this beyond just a theoretical idea, I’ve started turning this pattern into an open source framework called SymbiOSis which I’ve posted on GitHub and am continuing to refine.
If you’re interested in seeing how this sort of MVVM-but-better approach can work, check out the framework and the sample application. I welcome your feedback!
Posted in: architectureiosmvvmsymbiosis