So I followed the same approach when I first started iOS development. I actually had three entities, Model, View, Controller. And before you ask, no, the View wasn’t the Storyboard. It was a dedicated View class that does all the styling and is just a “dumb” entity that gets data and submits inputs.
However, we all know where this goes… as projects grow, the more complex the controller becomes, and the faster it becomes a “Massive View Controller.”
At some point I had to stop using MVC and switch to a different architecture. The question became… what are the other architectures available in iOS development?
- VIP (Clean Swift)
I’m sure you are all familiar with the above list. There are also some Redux-esque (looking at you React Native!) implementations in iOS (that I’m personally not really a fan of because they introduce unnecessary boilerplate and try to resolve a problem that does not exist within the iOS development environment). I’ve tried all of the solutions above. Each one has pros and cons but since they aren’t the topic of this article, I’ll sum it up in one line:
MVP is very verbose (lots of protocols); VIPER/VIP is overkill; MVC, you know, MassiveViewController.
MVVM – Do’s and Don’ts
“Give an iOS developer a screen to implement in MVVM and they will write 10+ implementations of how to handle ViewController-ViewModel interaction.”
There are tons of ways to implement MVVM but most of the implementations I have seen are sort of “home-made” implementations.
This is what you usually see on projects that claim to use MVVM, but they are just wrapping the ViewModel operation results via closures, and all functions of the ViewModel are exposed and public. This is a valid approach if you just want to get started, but this is definitely not the correct way to implement MVVM. The reason is very simple – MVVM is all about data-binding and this achieves zero data binding. If you find yourself calling viewModel.login(username: username, password: password) from the ViewController you need to rethink your implementation.
One of the other implementations I have also seen for MVVM is via protocols, which is just plain wrong. You are basically doing MVP and renaming the Presenter to ViewModel. In a proper MVVM implementation, the ViewModel should never be aware of the View that’s observing the data.
Other implementations rely on some external classes; some people call it “Dynamic”, some people call it “Box” (which is just a closure wrapped into a class), so we are sort of back to “home-made” implementations.
So the question is, how do we achieve proper MVVM? The answer is Functional Reactive Programming (FRP).
There are tons of libraries that introduce FRP to iOS development, such as:
It’s pretty apparent with the introduction of SwiftUI, Combine that Apple is pushing the approach of application architectures being FRP oriented. But what’s really awesome about FRP is that it’s sort of “learn once, apply anywhere.” Your knowledge will be transferable between different frameworks, and even between different platforms!
In this next part we’ll be focusing on implementing MVVM using RxSwift. Be warned, we assume you have a fair bit of knowledge of RxSwift, so we won’t be starting from scratch.
MVVM – Implementation using RxSwift
RxSwift can be a pain to deal with at first, but once you get used to it you’ll hate going back to normal “closures” (Chris Lattner, can we get async await? Please?).
Unfortunately there’s no clear implementation of MVVM-Rx that covers all of the cases. Most guides use BehaviorRelay (formerly Variable) and they still expose functions to ViewController. They also do things like viewModel.isLoading.accept(true), which is also a sort of “home-made” implementation because you are exposing unnecessary information to the ViewController. The ViewController should never be aware of anything except for the “Input” it’s binding to and the “Output” that’ll drive the UI.
First approach — without Subjects
Implementing this specific FRP-oriented MVVM requires you to shift your mindset away from regular programming, and into thinking if everything as an “Input” and “Output.” For starters, we’ll have our generic protocol that’ll be implemented by any ViewModel:
On paper, this looks very good: any ViewModel implementing ViewModelType will have to introduce an Input Struct, an Output Struct, and a function to transform Input to Output.
The above example shows a ViewModel implementation for a ViewController that creates a “Post.” As you can see, it has a set of inputs (cancel, save, title, details), and outputs two events (dismiss, saveEnabled).
Let’s take a look at the ViewController implementation that’s driven by this ViewModel:
Neat and clean. The ViewController binds to the inputs, calls transform, and then gets driven by the output. But this isn’t really practical: imagine you have the exact same screen but implemented using UITableView. So essentially a cell for the textField and a cell for the textView. How are we going to bind the input in viewDidLoad? Or drive the output to a UITableViewCell? It’s pretty much impossible.
While this approach of “transforming” is clean and doesn’t have any extra subjects created, it has limitations. A ViewModel with such an interface will only work with Views that are able to gather at the same time.
Second approach — with Subjects
Since we now know that it’s not possible to feed the ViewModel with all of the inputs at once in complex screens, we’ll modify the ViewModelType protocol to provide more flexibility:
Now we have exposed the Input / Output for the ViewController and it’s up to the ViewController when to provide the inputs, and when to subscribe to the outputs.
To link the Inputs / Outputs we’ll use Subjects. A Subject is a kind of bridge that acts both as an Observer and an Observable. Knowing that the ViewModel has 4 inputs (Cancel Button, Save Button, Title TextField, Details TextView) we’ll need four subjects. The ViewModel implementation won’t change that much we’ll be just migrating from regular transform function to the initializer of the ViewModel:
Now let’s take a look at the re-implementation of ViewController driven by this ViewModel:
Nice, neat and clean, nothing has changed. The ViewController isn’t aware of anything except the exposed interface, which is the input / output variables. We internationally split the binding into two functions, to demonstrate that it’s possible to bind the inputs anywhere and drive the outputs anywhere. We are not restricted by anything which allows us to plug this ViewModel into literally any screen that has the same inputs and gets driven by the same output.
But is this practical ? The answer is no.
All of the transformations happen at the init. So, if you have tons of transformations or complex functions depending on each other, your init function will be monolithic and you’ll quickly lose readability. And since input/output are uninitialized variables, you’ll have to initialize them before calling any functions. For example, you can’t separate the savedEnabled into a separate function and do something like this:
The compiler will complain and throw an error “Property ‘self.input’ not initialized at super.init call
Third approach — Using Subjects with a twist.
The third and final approach ensures everything as is, but also provides complete freedom to define functions as you wish in no specific order and initialize the input/output whenever you like. We can achieve this by using the infamous force unwrap (bang) (!) operator.
Calm down I also hate it… but everything has its use, and in this specific case it’s completely ok to use the force unwrap operator.
Let’s take a look at the final implementation of our ViewModel using force unwrapped versions of Input / Output. First thing first, we’ll modify our ViewModelType protocol requirements to be the following:
We did nothing except adding force unwrapping for Input and Output. Now let’s take a look at how this very small change can drastically improve the readability of our ViewModel:
This one minor change allowed us to separate each transformation into its own function, thus allowing composition of functions.
What’s really good about this is that the ViewController implementation remains as is. It does not care if you are force-unwrapping or whatnot. The same implementation on the figure above is still compatible with our ViewController from the second approach. We also changed our input/output variables to be private(set) to disallow any modifications from outside the ViewModel, since we turned it into a var.
So far we have learned how to shift our mindset from normal programming paradigms to FRP oriented programming paradigm, and we have implemented a sample demonstrating how to use RxSwift to achieve proper MVVM implementation. But this isn’t everything.
A new set of questions that might come to mind would be: how do I do activity indicators? How do I track errors? How are you using PublishSubject when it terminates on error?”
If you are interested in getting the answers, stay tuned for part 2 to learn about advanced MVVM-Rx, how to handle errors, and how to show/hide activity indicators without hacking using BehaviorRelays.