Building Breather (Part 2): Refactoring with MVVM and RxSwift

Breather is an open-source iOS app that shows the weather, air pollution and asthma conditions around you.

Alexandros Baramilis
10 min readApr 24, 2019

To raise awareness about air pollution and practice my iOS skills at the same time, I’m building an open-source app where I apply some of the latest knowledge I gain about iOS, Swift and programming. Since there is a lot of information to digest, I’m splitting it into small manageable parts:

MVVM

At the beginning of my iOS journey, I had read about MVC (Model-View-Controller), but I didn’t really pay much attention to it. I was like: I don’t care about this fancy stuff, I just want to write code.

After my first app ended up having 3 view controllers with 3000+ lines of code each, I started to think more seriously about architecture. I had to learn it the hard way.

So I started doing MVC, or more like my own version of MVC. I read about other developers complaining about MVC and calling it Massive-View-Controller. But my view controllers were pretty slim. I even started calling my pattern for jokes NMVC, for Not-Massive-View-Controller.

What was I doing differently? I basically had my main model and then I had some smaller models, which I called submodels. Each submodel was responsible for the data for one type of view. The submodels were created by the model. The view controller requested the submodels and passed them to the appropriate view for formatting and displaying the data.

After reading about MVVM, I thought it sounded quite similar to what I was doing. Mainly the fact that you have a model for each view. In practice, there were many differences, but the fact that I leaned naturally in that direction without even reading about it first, speaks a lot about this pattern.

That being said, I don’t believe in following any particular pattern blindly. Every project is different and has different needs. What matters the most, is that you write good and clean code, no matter what pattern you use.

But anyway, what is MVVM?

I don’t intend for this to be a tutorial on MVVM since there is a lot of material online (ex. here), but I will do a quick intro, starting with the usual graphic comparison :P

MVC vs. MVVM

With MVC in iOS, the centre of power is in the Controller, whose part is played by the UIViewController family. They own the View (played by the UIView family) via Storyboards and IBOutlets, by creating them programmatically and keeping references, etc and talk to them to update them. The View also talks back, to inform the Controller of any changes, ex. when the user taps on a button. The Controller also owns the Model (by holding a reference to it), talks to it (for example to request data), and the Model talks back via delegates, callback closures, etc.

In MVVM, the Controller is stripped of its power and merged with the View family and there is a new family called View Model which is now the centre of the action. The previously powerful UIViewController family is responsible now only for updating the UIView family with data that it gets from View Model and delegating user actions from the UIView to the ViewModel (and maybe some other UIViewController specific stuff). The UIViewController family never speaks to the Model directly, but they do own the ViewModel which owns the Model. This way responsibilities are split a bit more evenly and testing is easier since View Models are not so strongly coupled with UIViews as UIViewControllers are.

(In case you didn’t get the hint, I’ve been watching and reading a lot of Game of Thrones these days)

RxSwift

RxSwift is the Swift implementation of ReactiveX, an API for asynchronous programming with observable streams.

Again, there are many great tutorials online. I particularly enjoyed the video course by raywenderlich.com and the RxSwift documentation on Github is one of the best I’ve seen. Just have a quick glance at the Why section of the documentation to see the benefits of using RxSwift.

A similar great library is ReactiveCocoa, built on top of ReactiveSwift.

ReactiveSwift/Cocoa had a big head-start over RxSwift, particularly due to their great UI bindings, but RxSwift has caught up quickly through work on RxCocoa, and has probably now surpassed ReactiveSwift/Cocoa in popularity, as Google Trends suggests.

Google trends for RxSwift/Cocoa and ReactiveSwift/Cocoa

RxSwift’s popularity is also due to the fact that ReactiveX is a whole ecosystem supporting many languages, platforms and frameworks, which fosters collaboration as different developers can speak a common language.

RxSwift/RxCocoa shines when used alongside with MVVM, especially when you structure your view models as pure functions with inputs and outputs.

Kickstarter has popularised this pattern by open-sourcing their app. They also published some great talks (here and here) on how they use MVVM with RxSwift in their test-driven development (TDD) process. Realm academy also has a good talk on MVVM & RxSwift and their benefits, and BlaBlaCar write about how they use MVVM with RxSwift and how they feed their view models.

And now I’m gonna do my take on this pattern with Breather :)

Refactoring Breather with MVVM and RxSwift

In Part 1, we had a simple MVC pattern, with Main.storyboard being the View, MainViewController being the Controller, and CityConditions being the Model.

In Part 2, we have Main.storyboard and MainViewController being the View, we have a new file called MainViewModel which is the… ViewModel and CityConditions is still the Model.

Even though we don’t have a lot of files in this project, I’ve made folders for View, ViewModel and Model to show the separation. You can keep the controllers and the views in the same View folder, or you can separate them in different View and Controller folders if you wish. Also, if you have a project that starts to get more complex, you can create subfolders in each folder, grouped by app features or screens.

The only thing that changed between Part 1 and 2, is the MainViewController and the addition of the MainViewModel.

I will go though some code comparisons between Part 1 and 2 to try to make it more clear.

What changed in MainViewController?

1. We have a viewModel property

The viewModel dependency (Part 2 code)

To initialise this property, we can either instantiate it on the spot (var viewModel = MainViewModel()), or to be more future-proof (ex. we want to add tests with a mock view model in the future), we can use dependency injection.

Initialising the view model with dependency injection (Part 2 code)

You can read more about dependency injection with Storyboard in my other article.

2. We don’t need IBActions anymore!

In Part 1, we had this IBAction:

IBAction for UISegmentedControl valueChanged (Part 1 code)

Whenever the value of the segmented control for the AQI standard changed, we updated the air quality UI.

With RxSwift, we can simply observe the selectedSegmentIndex property of the segmented control by binding it to our view model input (more on this later).

Binding the selectedSegmentIndex property of UISegmentedControl (Part 2 code)

With RxCocoa’s UI bindings we can follow the same pattern for any type of control actions, taps, etc.

3. The MainViewController is no longer responsible for the data.

In Part 1, we loaded the view controller with sample data at the point of initialisation and then we called a function to update the UI, as seen in Part 1 — Bonus. You could also request the data from an API, or through a data source object, etc

Initialising with sample data and calling updateUI (Part 1 code)

In MVVM, the view controller does not care where the data comes from. We just need to bind the view model and call refresh().

In MVVM, we just need to bind the view model and call refresh (Part 2 code)

4. Binding the view model

Most of the code in MainViewController is about binding the view model. Binding the view model means that we bind the inputs and outputs of the view model, so that:

  • When an output in the view model is updated, the corresponding part in the UI will be updated automatically.
  • When a property in the view controller is updated, the corresponding input of the view model will be notified.

Inputs

Here, our view model inputs are viewDidRefresh and aqiStandard, of type AnyObserver<Void> and AnyObserver<Int> respectively.

To notify the view model that the view needs fresh data, we create the refreshSubject of type PublishSubject<Void>. A Subject in RxSwift is a type that can act both as an Observer and as an Observable. A PublishSubject emits all new elements to subscribers, which is the behaviour that we want here. Whenever the refresh method is called, we emit a void next element through the refreshSubject, notifying the view model that we need fresh data through its viewDidRefresh observer.

Here, I created a separate refresh method instead of calling onNext in viewDidLoad because I’m planning to use that method as a central point to refresh the UI through various actions, like applicationWillEnterForeground and pull to refresh, as described in Part 2 — Bonus.

The second input of the view model is the aqiStandard. Here we can bind the input directly to the selectedSegmentIndex of the rx control property. Whenever the selected segment index changes, the view model will observe the index through the aqiStandard observer.

Binding the view model (Part 2 code)

Outputs

Binding the outputs is quite straightforward. Wherever we have an rx binder, we bind that property to the view model output, so whenever the output is updated, the corresponding UI element will be updated as well.

We defined our view model outputs as Drivers. A Driver is an RxSwift trait, which is like a specialised observable. In this case, it is specialised to “drive the UI”, so it never errors out and is always observed on the main scheduler.

The drive method works like subscribe. We can use it to bind the output directly to an rx binder, or we can run code in the onNext handler when there is no binder or we need some custom behaviour.

The MainViewModel

We can create a simple protocol that our view models will conform to, to enforce this input and output pattern.

The ViewModel protocol

Now, I’ll just dump the whole view model here (sorry!) and then I’ll go through it bit by bit.

The MainViewModel

Inputs

In the Inputs section, we define an input let of type Input and then define the Input struct that contains the inputs as described previously. This pattern allows us to have all the inputs easily accessible from the view controller, using the “viewModel.input.someInput” notation.

Now, we also define some Subjects that correspond to each input. This is because we need them to act both as Observers (observing UI actions) and as Observables (so we can subscribe to them and react accordingly in the view model).

You might ask, why don’t we define them as Subjects in the first place, instead of AnyObserver. This is because we want to keep the Observable side private, so no one else can “emit” input to our view model. This is why we make our Subjects private, but keep the inputs exposed.

In the init() method, we simply initialise the inputs by setting them to the corresponding subject .asObserver()

For the viewDidRefreshSubject, a PublishSubject<Void>, does the job well, but for the aqiStandardSubject we need a BehaviorSubject<Int>. A BehaviorSubject always returns the latest value, even to new subscribers (vs. PublishSubject which will only return new values, so if you’re a new subscriber, you miss the latest value). This is because we need an initial value for the segmented control when we’re updating the UI for the first time.

Finally, in the init() method, we also subscribe to the viewDidRefreshSubject and we map the void elements to the sample data from CityConditions. Here you would have instead the whole chain of request from API, etc. Whenever the view requests fresh data, we get the sample data and emit it via a next event on the cityConditionsSubject that we defined privately. We will use this subject to format all our outputs.

Outputs

In the Outputs section, we define all our outputs in a struct, similarly to what we did with the Inputs, but in this case we define them as Driver, because we will use them to update the UI.

In the init() method, we do all the appropriate formatting of the data (what we used to do in view controller before) and initialise the Output object.

The formatting is pretty straightforward, just mapping the data from cityConditionsSubject to the appropriate output.

When the data depends on the value of the segmented control, we do a combineLatest operation, which combines the latest elements from the cityConditionsSubject and the aqiStandardSubject into a (CityConditions, Int) tuple. Then based on the Int value, we choose the appropriate US/China data from CityConditions.

Since the Driver never errors out, we also have to provide a value in case of an error. In this case I provided the default initial values of the UI.

And that’s it!

Keep reading → Part 2 (Bonus): Refreshing the UI with loading state and mocking API requests with delay.

--

--