What is MVVM?

MVC is a fine pattern for the simplest apps. When you build something more complex you need a better architectural and design approach for your codebase than that. One of the alternatives that is embraced by the iOS community is MVVM. This question inevitably will arise through conversations about architecture and design. Answering it well will make you stand out from the crowd because, to my knowledge, not that many developers actually use this very useful design pattern.

MVVM stands for Model-View-View-Model. This design pattern is effectively a subset and extension of MVC. With MVVM on iOS, in addition to models, views, and controllers, we’d also have view models that play an important role in data presentation and delegating business logic trig- gered by the view layer (views and view controllers). It fits nicely into existing MVC architectures and extends it by making it more testable and less coupled.

To illustrate where and how we use view models, consider this example:

import UIKit
class MyViewController: UIViewController {
    private var someButton: UIButton!
    private var buttonTitle: String!
    convenience init(buttonTitle: String) {
        self.init()
        self.buttonTitle = buttonTitle
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        self.someButton = self.setupButton(title: self.buttonTitle)
    }

    private func setupButton(title: String) -> UIButton {
        let button = UIButton()
        button.setTitle(title, for: .normal)
        button.addTarget(self,
                         action: Selector(("buttonClick:")),
                         for: .touchUpInside)
        return button
    }

    public func buttonClick(sender: AnyObject) {
        // let's assume there's some business logic happening here print("there's an action here that
        //relies on the state of your application")
        print("such as button title - for example, \(self.buttonTitle!)")
    }
}

// creating a new instance of our VC
let myVC = MyViewController(buttonTitle: "some button title") // simulating our VC's appearance on the screen
myVC.view
// simulating the user clicking our button
myVC.buttonClick(sender: NSObject())
// output:
// there's an action here that relies on the state of your application
// such as button title - for example, some button title
        

As you can see there’s typical MVC stuff going on here. We have a view con- troller that upon initialization takes in some state to be displayed in its button title when the view is loaded. And it also prints that title when the user clicks on that button.

Seems ok, doesn’t it? Well, there’s a responsibility and coupling problem here that quite often leads to a “Massive View Controller” issue. The problem is that our view controller is a view layer object that is responsible for displaying a UI, but it currently also tries to get into the business of managing state and executing business logic. The way it does it is by keeping a reference to state, in our case the buttonTitle string that is passed to it upon initialization, and by having the business logic in the buttonClick method.

A way to improve is to introduce a viewmodel object for business logic and state and inject it as a dependency in our view controller:

import UIKit
class MyViewModel {
    let title: String
    init(title: String) {
        self.title = title
    }

    func printAction() {
        print("executing business logic")
        print("printing button title: \(self.title)")
    }
}
class MyViewController: UIViewController {
    private var viewModel: MyViewModel!
    private var someButton: UIButton!
    convenience init(viewModel: MyViewModel) {
        self.init()
        self.viewModel = viewModel
    }

    override func viewDidLoad() {
        114
        super.viewDidLoad()
        self.someButton = self.setupButton(title: self.viewModel.title)
    }

    private func setupButton(title: String) -> UIButton {
        let button = UIButton()
        button.setTitle(title, for: .normal)
        button.addTarget(self,
                         action: Selector(("buttonClick:")),
                         for: .touchUpInside)
        return button
    }

    public func buttonClick(sender: AnyObject) { // trigger business logic here self.viewModel.printAction()
    }
}
// creating an instance of MyViewModel to keep track of state // and to execute business logic
let myViewModel = MyViewModel(title: "some button title")
// creating a new instance of our VC and injecting our viewmodel
let myVC = MyViewController(viewModel: myViewModel) // simulating our VC's appearance on the screen myVC.view
// simulating the user clicking our button
myVC.buttonClick(sender: NSObject())
// output:
// executing business logic
// printing button title: some button title
        

So what we did here was extract all the state (title string) from our view controller since it’s not the view controller’s responsibility to keep track of the state. And we extracted the business logic (printAction that used to be in its buttonClick method) from it as well. The reason is the same, it’s just a UI and it shouldn’t know what our app does. There are many benefits of extracting that stuff into a view model object: state decoupling, responsibility decoupling, and better testability of your code. Now to test your business logic and state changes, you don’t have to instantiate the view controller; all you have to do is to create a view model, send messages to it, and examine the changes. The view controller simply becomes an input device, just like terminal or voice is.

You can also do the same thing with other UIView subclasses - not only view controllers and create view models for them when they grow out of proportion and carry too much state and logic.

This in a nutshell is what the MVVM pattern is and what it helps with in your code. For me personally it’s been an invaluable tool for refactoring and code improvement that I’ve used when joining projects. Slimming down view con- trollers is typically the lowest-hanging fruit for improvement on iOS projects.