MVVM architecture: a step-by-step guide

7 min read
February 15, 2017

What a difference a ViewModel makes.

Choosing the right app architecture is an important task, and as it usually goes, it’s not always the easiest one.

Good architecture should make it easy to do bug fixes, add new features, and help speed up the development process, which in turn translates to less development costs, both in the short and long run.

For the reasons stated above, MVVM (or MVVM-C) works best for us, and we’ll explain how we use it in a little while. But first, let’s go over why we decided to move away from the very architecture Apple provides — MVC.

decoding


The trouble with MVC

Since iOS was initially based on the MVC architectural pattern, it seems like the logical first choice, and at first glance, it’s definitely straightforward enough.

The pattern divides the software into three different parts, where Model represents data, View represents user interface and the Controller acts as the glue between the two. This seems straight-forward enough, but as the apps grow in complexity, this approach shows its shortcomings.

As per Apple’s instructions, everything in the MVC pattern should be classified as either Model, View or Controller, but this approach leaves you asking: Can everything really be neatly sorted into one of the three, without seriously overloading one of the components?

All of the code and logic that’s hard to place ends up in the ViewControllers, both making them massive and littering them with too many responsibilities. In turn, they become non-reusable, difficult to maintain, more prone to bugs and harder to test.

Why go with MVVM?

MVVM helps tackle that problem by providing a clearer set of rules and breaking down the app logic into smaller, easily manageable chunks.

Here Model contains all the business logic, like getting the data from the database or API, parsing the data and dealing with some custom logic. It’s in charge of business rules, data access, model classes etc.

View represents the entire user interface visible to the user and comprises both UIViewController and UIView.

ViewModel prepares all the information for the user interface. It communicates with the ViewController through a defined protocol and provides an easy way to test the entire ViewController.

In this approach, ViewController serves as a bridge between ViewModel and View and it doesn’t know anything about the business logic. Basically, it’s data-agnostic.

As a result, ViewControllers are strictly defined, which makes debugging and reusing them much easier.

So, how does that actually work?

The biggest improvement is that the MVVM architecture allows you to better organize the code into smaller logical chunks.

At DECODE we separate every screen into multiple UIViewControllers based on the Composition Pattern.

Let’s say a project we’re working on has a screen showing current user profile, like the one in the Facebook app, and that it has the following logical units:

  • User profile header
  • Photos section
  • Friends section
  • About section

This is how we would organize the hierarchy:

The main function of the UserProfileContainerVC is to lay out others ViewControllers, where each child ViewController is responsible for one specific feature, adhering to the Separation of concerns principle.

By adopting ViewController composition, we can break down one complex ViewController into many simple ViewControllers.

Every ViewController is in turn formed of the following elements, each of them provided in a separate file:

  • View
  • VM (ViewModel)
  • VMProtocol (ViewModelProtocol)

Model

The Model in MVVM comprises the Business Layer and API layer. It’s a pretty important and complex topic that can hardly be summed up and explained in a couple of gists, so we’ll dedicate one of the future posts to it.

In the meantime, let’s just go over it quickly. The Business Layer here is in control of Data Model and other services that do some specific logic that can be extracted and encapsulated.

The API layer implements the protocols defined by the Business Layer and is also in the charge of all HTTP calls to the server. It also has the ownership of handling the access token. It implements the logic for getting the token from the server, storing it and using it with every HTTP request. 

View

This is a UIView subclass and its task is to define a layout for a specific ViewController.

Initialization of all buttons, labels, and constraints comes inside of this class and its task is to provide all UI components to the ViewController through the exposed getters.

public class UserProfileHeaderView: UIView {
    public let avatarImageView = UIImageView()
    public let backgroundImageView = UIImageView()
    public let fullNameLabel = UILabel()
    public let subtitleLabel = UILabel()
    public let friendRequestButton = UIButton()
    init() {
        super.init(frame:CGRect.zero)
        /*
        configuration of views, constraints, frames etc.
        */
    }
}

If you need to change anything regarding design or layout, later on, this is the only place you have to look. The ViewController loads a custom view described in the paragraph below. The basic idea here is to separate the initialization of UI elements and layout from handling various events. In this approach, ViewController is just an intersection between UI elements and the data prepared by its ViewModel. It does not, in any way, need to know anything about the Business Layer and API layer.

public class UserProfileHeaderVC: UIViewController {
    private var userProfileHeaderView: UserProfileHeaderView {
        return self.view as! UserProfileHeaderView 
    }
    private let viewModel: UserProfileHeaderVMProtocol = UserProfileHeaderVM()
    override func loadView() {
        /* Here we connect view controler with our custom view */ 
        view = UserProfileHeaderView()
        /* View controller doesn't need to know anything about layout,
        constraints etc. 
        View controller's task is to handle all events provided
        by user interface */ 
        userProfileHeaderView.friendRequestButton.addTarget(
            self, 
            action: #selector(addFriendRequestAction(_:)),
            forControlEvents: .TouchUpInside )
        /* By calling this method, view controller takes data 
        from its view model  and update its state with the latest data.
        When you need to refresh the data shown by this view controller, 
        all you need is to call this method. Everything else you get for free.
        */
        bindViewModel()
    }
    private func bindViewModel() {
        /* View controller uses its view model to get the correct data */
        userProfileHeaderView.avatarImageView.image = viewModel.avatarImage
        userProfileHeaderView.backgroundImageView.image = viewModel.backgroundImage
        userProfileHeaderView.fullNameLabel.text = viewModel.fullName
        userProfileHeaderView.subtitleLabel.text = viewModel.subtitleText
        userProfileHeaderView.friendRequestButton.enabled = viewModel. friendRequestButtonEnabled
    }
    @objc private func addFriendRequestAction(sender: UIButton) {
        /* we call method on our view model which contain some logic for making this request. */
        viewModel.sendFriendRequest()
    }
}

To make data binding easier between view controller and its view model, we often use ReactiveCocoa/RxSwift. We’ll also go into more detail on using the Xcode InterfaceBuilder (Xib files) with this architecture in the following weeks. 

ViewModel

The role of the ViewModel is to prepare the data for presentation, making sure there is no tight coupling between the Model and the View.

When we implement this architecture, we use VMProtocol, because it makes it easier for us to administer any necessary changes without additional fuss and to mock all data needed for acertain ViewController before we have the real deal to implement.

You can think about VMProtocol as a contract between UIViewController and the BusinessLayer.

An excellent rule of thumb is to check if some of your code implemented in VM requires UIKit. If it does, this is a big warning.

Exceptions for this rule are UIImage and CGFloat.

public protocol UserProfileHeaderVMProtocol {
    public var fullName: String { get }
    public var subtitleText: String { get }
    public var backgroundImage: UIImage { get }
    public var avatarImage: UIImage { get }
    public var friendRequestButtonEnabled: Bool { get }
    public func sendFriendRequest()
}

Implementation of ViewModel often calls classes from the Business Layer, it communicates with application data model etc.

public class UserProfileHeaderVM: NSObject, UserProfileHeaderVMProtocol {
    /*
        Here the class implements UserProfileHeaderVMProtocol protocol. 
        Implementation is application (project) depended and 
        it doesn't influence this architecture. 
    */
}

Now, onto the MVVM-C

MVVM is a great pattern, and should make your life easier once you get a grasp on it. However, it’s not a universal answer to all your problems.

Stay tuned for the next post, when we talk more about MVVM-C, C being the Coordnator.

Categories
Written by

Marko Strizic

Co-founder and CEO

Marko started DECODE with co-founders Peter and Mario, and a decade later, leads the company as CEO. His role is now almost entirely centred around business strategy, though his extensive background in software engineering makes sure he sees the future of the company from every angle. A graduate of the University of Zagreb’s Faculty of Electrical Engineering and Computing, he’s fascinated by the architecture of mobile apps and reactive programming, and a strong believer in life-long learning. Always ready for action. Or an impromptu skiing trip.

Related articles