An Introduction to ReactiveSwift

Patrick Wiseman
Making Mercari
Published in
5 min readApr 23, 2018

Hi, I’m Patrick — an iOS Engineer at Mercari. Here at Mercari, our client engineering teams use the reactive programming paradigm. In recent years, this paradigm has grown and become more popular within the development community. In this article, I’d like to provide a brief intro to reactive programming using ReactiveSwift. To demonstrate these concepts, I’ve provided a sample project for you to reference alongside this post; it simply shows a random color when a button is pressed, but it illustrates how reactive programming can be used. Please note that this article assumes basic proficiency in Swift.

At Mercari, and in this sample project, we use Model-View-ViewModel (MVVM) architecture along with ReactiveSwift. The ReactiveSwift library allows us to more easily apply the reactive programming paradigm. At the heart of reactive programming is the concept of a stream — a stream simply sends values and, when we observe them, we can take action. In ReactiveSwift, this concept of a stream is represented by the Signal class. We can model user interactions as signals. This pairs well with our architecture of choice, MVVM.

The ViewModel

The ViewModel is one of the most integral parts of MVVM. Any logic that must be performed should live inside the ViewModel. This clearly separates responsibilities throughout an application: ViewModels handle the logic, while views and ViewControllers are updated based on the output of this logic. It’s easy to represent this pattern as inputs and outputs within the ViewModel layer. Here is how this looks at a high level:

Inputs

You can think of inputs as actions taken, whether performed by the ViewController itself (viewDidLoad) or the user (taps and swipes); we want to act on them to perform some work. In the sample project, we can see how input is captured. As seen below, the RandomColorViewModelInputs protocol declares functions that will be called when certain action is taken:

protocol RandomColorViewModelInputs {
func viewDidLoad()
func newColorButtonTapped()
}

For example, viewDidLoad() is called when the viewDidLoad method fires inside our ViewController. We can make these functions reactive inside the implementation using ReactiveSwift’s .pipe(). This allows us to create a Signal that we can send input through and observe the resulting output. Here we can see the definition of viewDidLoadIO (our pipe, IO for input/output) and the viewDidLoad() (from our protocol) that provides the input:

private let viewDidLoadIO = Signal<Void, NoError>.pipe()
func viewDidLoad() {
viewDidLoadIO.input.send(value: ())
}

viewDidLoadIO is a signal that makes use of .pipe(). It sends a value of Void and has NoError as the associated error type (essentially meaning it will never error). Inside the function viewDidLoad() we can see our Void input is being sent through viewDidLoadIO. Here is how the overall flow through viewDidLoadIO looks:

Outputs

Outputs are the result of the actions taken. For example, if viewDidLoad is called, we want to generate a random color and display it to the user. There is only one output defined in this ViewModel. Inside our output protocol we can see the declaration of a single signal, displayModelSignal. It sends a struct that contains information about how our view should look:

Protocol RandomColorViewModelOutputs {
var displayModelSignal = Signal<RandomColorDisplayModel..> { get }
}

When our view first loads, we want to configure it; that’s where the viewDidLoadIO from above comes in. viewDidLoadIO.output is just a signal — it’s the other end of the input pipe. We can transform this signal to create thedisplayModelSignal, that when observed will provide updates to our UI. Don’t let this intimidate you; we’ll break it down step-by-step in the next section:

var displayModelSignal: Signal<RandomColorDisplayModel, NoError> {
return Signal
.merge(
viewDidLoadIO.output,
newColorButtonTappedIO.output
)
.map { RandomColorDisplayModel() }
}

The important thing to note is that by using the viewDidLoadIO.output signal, we can observe a value of Void when viewDidLoad() fires. A value of Void is not very helpful, but in ReactiveSwift we can use operators to transform the values sent by signals. In this case, Void would be mapped to a RandomColorDisplayModel struct.

Transformation

In the example above, we can see the computed property displayModelSignal — this is just the implementation of the signal declared in our RandomColorViewModelOutputs protocol. The returned signal is composed by two operators: merge and map. It looks a bit complicated, but let’s break it down:

Signal
.merge(
viewDidLoadIO.output,
newColorButtonTappedIO.output
)

Signal.merge() takes whatever signals we provide and merges them into a single signal. This new signal will send Void when viewDidLoadIO.output or newColorButtonTappedIO.output send Void. This also means that, within the merge declaration, the values produced by our signals must be the same. This brings us to the map operator:

.map { RandomColorDisplayModel() }

Just like with a collection, map allows us to transform each value sent by a signal. In this case, it’s fairly simple — when our merged signal sends a value (Void) we return a newly initialized RandomColorDisplayModel. More complicated examples may call for transformation based on what values are sent.

Observing Change

Now we have inputs and outputs in our ViewModel. We just need to send input and observe output inside the ViewController, which can been seen insideRandomColorViewController. There is a declaration of the viewModel at the top (using our RandomColorViewModel):

final class RandomColorViewController: UIViewController {
let viewModel = RandomColorViewModel()
}

Using this ViewModel, we can capture inputs. In order to create outputs, there must be inputs. Therefore, when action is taken, we want to call the corresponding input method. Inside viewDidLoad, we call the viewModel.input.viewDidLoad() method. As discussed above, this will send a value of typeVoid through our viewDidLoadIO pipe:

override func viewDidLoad() {
super.viewDidLoad()
bindViewModel()
viewModel.inputs.viewDidLoad()
}

Now all we have to do is observe these changes, this where bindViewModel() in the code snippet above comes in. This method allows us to configure the observation of output and the subsequent actions taken based on this output. All output signals from the ViewModel can be observed using .observeValues. This allows us to create a closure that has parameters of the values being sent inside the signal. The closure will take these values as parameters and perform updates:

private func bindViewModel() {
viewModel.outputs.displayModelSignal.observeValues { [weak self] in
self?.colorDescriptionLabel.text = $0.description
self?.colorView.backgroundColor = $0.color
}
}

Since work on self is being done in this closure, we should weakify self — hence the [weak self]. Also note that $0 is an anonymous closure argument — in this case it represents the RandomColorDisplayModel struct. With these details in mind, the body of our closure simply takes the struct sent by the signal and applies it to the ViewController. The background color and label text are set using the values on the RandomColorDisplayModel. This process is performed every time a value is sent through our displayModelSignal.

Conclusion

Although this is a fairly simple example, it demonstrates an application of reactive programming. It applies a strongly defined and repeatable pattern. The UI updates are cleanly modeled and handled in a central location. Of course, as the UI grows in complexity, it will make sense to break the displayModelSignal into smaller, more manageable parts. I hope this article has inspired you to give it a try. Thanks for reading!

--

--