Giles Van Gruisen

Model-View-ViewModel with Swift

iOS view controllers can be dangerous. They often don't have a single, clear responsibility. They get bloated and can turn into a catch-all for everything from data loading and formatting to view configuration and layout code. Yuck! These kinds of view controllers are untestable, hardly reusable, and not very approachable for newcomers.

The view controller's responsibility is to load and configure a view as well as ensure any navigation is handled appropriately.

In order to avoid view controller bloat and have cleaner, leaner components, we need to take a slightly different approach to application architecture. Let's try to think about our application and view controllers in terms of data flow.

Architecting data flow

We can't always just shove the model data into our UI elements. That works for text (sometimes), but not for things like images and dates where the storage format differs from the display format.

Let's break this down a little. Say we're pulling some data from a server that needs to be displayed to the user. What needs to happen between the time the data is fetched from a store or server and the time it is presented to the user? It needs to go through a series of transformations before eventually populating the view.

Everything the user sees or edits is derived from that model data. We'll want to architect our application in such a way that allows for easy flow and transformation of our model data into more presentable values. There should be a clear path that can be repeated if data is refreshed or updated.

Bring on the view-models

Fortunately there's a pattern called Model-View-ViewModel (MVVM) that affords this kind of deliberate data flow. It defines a relationship between views, models, and another kind of object called a view-model. A view-model is a simple object that takes a model, transforms its data into more presentable values, then spits it back out to view elements to be rendered and presented to the user.

When the view-model receives notice of an update (succesful fetch from store or server), it performs its transformations with the model's data and sets its own properties to the derived values before notifying any views. But how does the view-model get its data in the first place? And how does it notify the views?

We need some way to move the data from the model to the view-model and then from the view-model down to the view(s). I like to link all of these objects (models, view-models, views) with a simple publish-subscribe relationship. This allows us to have any number of views subscribed to a single view-model, as well as generally improve reusability between views, models, and view-models.

MVVM w/ publinks

I wrote a small library called Publinks to form this publish-subscribe link between objects. Publinks are light objects that broadcast a value to their susbcribers when publish(value: ParameterType) is called. To subscribe to a publink:
publink.subscribe(subscriptionBlock: ParameterType -> Void).

ParameterType is a generic type set upon initialization e.g. Publink<Int>(). This makes publinks incredibly safe, because publishers must publish a value of type ParameterType and the argument passed to subscribers is guaranteed to be of type ParameterType.

This type can be anything. It can even be an optional, Any, or Void, but the advantage of setting an explicit type is to avoid nil-checks, downcasts, and the potential for empty values.

Building the objects

We want one publink on each model and view-model. The model's publink will be publishing that model, so the publink's ParameterType should match the type of the model. Similarly, the view-model's publink will be publishing that view-model, so the publink's ParameterType should match the type of the view-model. Let's look at an example to see the relationship between model, view, and view-model.

Once the data has loaded, the PostModel publishes itself to publink:

class PostModel: NSObject {  
    …
    var publink = Publink<PostModel>()
    …
    func dataDidLoad() {
        // Data loaded and model fully populated, publish self
        publink.publish(self)
    }
}

The PostViewModel holds a reference to a PostModel instance and, immediately after being set, subscribes to its publink. When postModel calls publish, the publink will call postModelUpdated(postModel: PostModel) as a subscription block. It also has a publink of its own to which views may subscribe:

class PostViewModel: NSObject {  
    …
    var postModel: PostModel {
        didSet { postModel.subscribe(postModelUpdated) }
    }
    var publink = Publink<PostViewModel>()
    …
    func postModelUpdated(postModel: PostModel) {
        // Perform transformations then publish to subscribers
        self.titleText = postModel.title.uppercaseString()
        dispatch_async(…) {
            let data = NSData(contentsOfURL: postModel.avatar)
            if let image = UIImage(data: data) {
                self.avatarImage = image
            }
            publink.publish(self)
        } 
    }
}

The PostView holds a reference to a PostViewModel instance and, immediately after being set, subscribes to its publink (just like the view-model subscribes to the model's publink). When postViewModel calls publish, the publink will call postModelUpdated(postModel: PostModel) as a subscription block.

class PostView: UIView {  
    …
    var postViewModel: PostViewModel {
        didSet {
            postViewModel.subscribe(postViewModelUpdated) 
        }
    }
    …
    func postViewModelUpdated(newPostViewModel: PostViewModel) {
        // Populate UI elements with values on newPostViewModel
        // e.g. self.titleLabel.text = newPostViewModel.titleText
        // e.g. self.avatarImageView.image = newPostViewModel.image
    }
}

Side note: you should always wait for the entire model to load before publishing to subscribers in order to avoid the danger of potentially empty models. If you want to transform and present data more progressively, i.e. as it comes in, you sohuld use multiple publinks per model. The same applies to view-models.

Now we have a clear path for data to follow:

PostView <- PostViewModel <- PostModel

Tying it all together

So now we have view-models handling data transformation and formatting, and views handling layout and presentation. Woohoo! Now we need a view controller to put the pieces together.

The view controller has its model set upon initialization or segue. When that model is set, we build a view-model (or look it up) and set that as the view-model of our view.

class PostViewController: UIViewController {  
    …
    var view: PostView!
    var postModel: PostModel {
        didSet {
            // Set view's postViewModel
            view.postViewModel = PostViewModel(post: postModel)
        }
    }
    …
}

Now if we decide to add a refresh control or something similar, there's very little (if any) work required to update the UI. Simply perform another fetch in the model and ensure new data is published when the request completes. That's it!

MVVM makes life easier

I've found there's a lot of benefit in having a clear flow of data and distinctly separate components performing smaller tasks. It's more testable, easier to debug, and definitely more approachable than long, bloated view controllers. It also incentivizes you to write smaller, more reusable components like view-models and isolated data sources.

If you want to read more about MVVM and app architecture, I recommend checking out Issue #13 of Objc.io. I also recommend watching the WWDC 2015 talk, Advanced iOS Application Architecture and Patterns.

I'd love to hear your feedback or if you have any way to make this stuff cleaner and safer. Feel free to reach out to me on Twitter!