I write a lot about making view controllers more digestable, and one very common pattern for doing that is called Model-View-ViewModel. I think MVVM is an anti-pattern that confuses rather than clarifies. View models are poorly-named and serve as only a stopgap in the road to better architecture. Our community would be better served moving on from the pattern.
MVVM is poorly-named
Names are important. Ideally, a name effectively messages what an object is for, what roles it fills, and how it’s used. “View model”, as a name, doesn’t do any of those things.
To make my case for me, the “view model”, on account how abstract it is, actually refers to two very different patterns.
The first type of “view model” is a “model for the view”. This is a dumb object (definitely a struct in Swift) that is passed to a view to populate its subviews. It shouldn’t contain any logic or even any methods. In the same way that a
UILabel takes a string, or a
UIImageView takes an image, your
ProfileView can take a
ProfileViewModel. It’s passed directly to your
ProfileView, and it crucially allows you to make your subviews private, instead of exposing them to the outside world. This is a noble and worthwhile end. I’ve also seen this pattern called “view data”, which I like, because it removes itself from the baggage of the other definition of “view model”.
“View model” is also a name for a vague, abstract object that lies in between a model object and a view controller. It performs any data transformation necssary for presentation, as well as sometimes networking and database access, sometimes form validation, and any other tasks you feel like throwing in there. This jack-of-all-trades-style object is designed to transfer weight away from your controllers, but ultimately, creates a new kitchen sink for you to dump responsibilities into.
MVVM invites many responsibilities
The lack of concrete naming makes this class’s responsiblities grow endlessly. What functions should go in a view model? Nobody knows! Just do whatever.
Let’s look at some examples.
- This post puts networking inside your view models, and recommends you add validations and presentation logic to it as well.
- This post only shows how to put presentation logic in view models, raising the question of why it’s not called a Presenter instead.
- This one says you should use them for uploading data and binding to ReactiveCocoa.
- This one uses them for both form validation and fetching data.
This one takes the cake by specifically suggesting that you put “miscellaneous code” in a view model:
The view model is an excellent place to put validation logic for user input, presentation logic for the view, kick-offs of network requests, and other miscellaneous code.
Nobody has any idea what the words “view model” mean, and so they can’t agree on what to put in them. The concept itself is too abstract. None of these writers would disagree about what goes in a Validator class, or a Presenter class, or a Fetcher class, and so on. Those are all better names with tightly defined roles.
Giving the same name to a wide variety of different objects with drastically different responsibilities only serves to confuse readers. If we can’t agree view models should do, what’s the benefit to giving them all the same name?
Our discipline has already faced a similar challenge, and we found that “controller” was too broad of a name to be able to contain a small set of responsbilities.
You’re totally free to give your classes whatever names you want! Pick good ones.
MVVM doesn’t change your structure
Finally, view models don’t fundamentally alter how you structure your app. What’s the difference between these two images? ( source)
You don’t need an advanced graph theory class to see that these are almost completely identical.
The most charitable thing that I can say about this pattern is that it changes your kitchen sink from a view controller, which is not an object that you own (because it’s a subclass of an Apple class), to a view model, an object that you do own. The view controller is now free to focus on view-lifecycle events, and is simpler for it. Still, though, we have a kitchen sink. It’s just been moved.
Because the view model is just one, poorly-defined layer added to your app, we haven’t solved the complexity problem. If you create a view model to prevent your view controller from getting too big, then what happens when your app’s code doubles in size again? Maybe at that point we can add a controller-model.
The view model solution doesn’t scale. It’s a band-aid over a problem that will continue to come up. We need a better solution, like a heuristic that allows you to continually divide objects as they get too big, like cells undergoing mitosis. View models are just a one-time patch.
Other communities have already been through this
The Rails community has gone through this problem some years ago, and they’ve come out on the other side. We could stand to learn from their story. First, they had fat controllers, and almost nothing but persistence in their models. They saw how untestable this was, and so they moved all the logic down into the model, and ended up with a skinny controller, and a fat model. The fat model, since it was reliant on ActiveRecord, and thus the database, was still too hard to test and needed to be broken down into many smaller components.
Blog posts like 7 Patterns to Refactor Fat ActiveRecord Models (which was the inspiration for my own 8 Patterns to Help You Destroy Massive View Controller) are an example of the product of this chain of thought. Eventually, you’re going to have to separate your concerns into small units, and moving your kitchen sink is only going to delay the reckoning.
View models are a solution that is totally unsuited to the challenges of modern programming. They are poorly-named constructs that don’t have a sense of what they should contain, which causes them to suffer the same problems as view controllers. They are only a temporary patch to a complicated problem, and if we don’t avoid them, we’ll have to deal with this problem again in the near future.