RxSwift — ViewModel done right

Côme Chevallier
Smoke Swift Every Day
4 min readAug 11, 2017

The blueprint you’ve been looking for.

Prerequisite:

Action is a great and small module used to abstract the concept of… an action in RxSwift. Check it out, and use it everywhere it fits

NB: goes hand-in-hand with ViewController done right and Coordinator pattern done right; they are part of the same whole.

In a nutshell

There are a lot of different ways one might structure a ViewModel, the same way one might structure an investment portfolio; there is no “best” way but there definitely are “better” ways. Let’s focus on what seemed to be the safest and most polyvalent option so far.

Some of the rules/patterns presented here might either seem weird or repetitive or overkill at first glance; bear in mind they:

  1. provide a consistent structure and API across ViewModels which you can confidently rely on today and 2 months from now
  2. are insurance against your future dumb self poking around the code trying to agile-esquely rush a new feature and smearing the code base in the process (it will of course never be refactored even though you knew and said it was just a “quick-and-dirty” hotfix that would be reintegrated in the blablabla)
  3. are of tremendous help in bringing existing and future colleagues up to speed on MVVM and RxSwift as well as enabling them to review your code more efficiently

What should never be in a ViewModel

Whenever you make an exception to these, think long and hard before doing so.

  1. A ViewModel should NEVER import UIKit, though extremely rare exceptions can be made when working with UIImage or another UI Type. In this case, then restrict to the bare minimum such as import UIKit.UIImage
  2. A ViewModel should never implement a DisposeBag, except for subscriptions that should be bound to the ViewModel's lifecycle (if any)
  3. Only use Variables where absolutely necessary: it's often only about re-writing a smarter .scan

Caveat: let’s be honest, your ViewModel will almost always have life cycles if you are doing more than the latest FartApp. Points 3 is more of a reminder to always hold back on creating Variable s, the use cases of which are almost always tied to point 2.

Recipe for a robust ViewModel

A ViewModel has only one mission: to transform inputs received from either dependency injection or its ViewController and expose outputs for its ViewController to bind to.

Let’s take a look at the basic structure and break it down from top to bottom:

The top 3 protocols define the purpose of the ViewModel. They follow simple rules:

  • Inputs always are of type PublishSubject<T>: somebody needs to push stuff with .onNext to the ViewModel which means inputs must be observers (duh). Sometimes, because you are smart and use Action , they might be of type InputSubject<T> which is basically the same except the latter cannot error out or complete
  • Outputs always are of type Observable<T>: somebody will observe the ViewModel (otherwise, well you don’t need one in the first place) and the only thing they need is a read-only stream to look at in order to react accordingly
  • Actions always are of type Action<T, U> or CocoaAction (which is just a typealias for Action<Void, Void>): you don’t really have a choice though, just don’t put anything else in there that’s not from the Action module

The MyViewModelType protocol simply enforces the need for the same three variables to be created every time, which basically are your API to the ViewModel.

These variables are implemented as computed and return self which is why down at the bottom the ViewModel needs to conform to their protocol specs. They are all the way down just to visually de-clutter the code.

Now that the ViewModel skeleton is in place, let’s get to the meat:

  1. Everything happens in the init: nothing gets initialized outside of it, all bindings are set up
  2. There always is a service for your ViewModel: it represents the “building blocks” helper struct that it can consume/assemble from to produce outputs [see Services done right]
  3. There (almost) always is a reference to the app coordinator: when the ViewModel is bound to by a controller. You do not need a reference to it when it is bound to by a view, such as a UICollectionViewCell
  4. There is nothing more than actions from the Action module below init: every “func” you imagine to produce output observables either sits in the service struct as a helper or can and should be abstracted as an action

Real life example

Send stuff to a sending list with a database upload and present a new scene.

Credits to Shai Mishali for inspiring the structure.

--

--