As a ReactiveSwift user myself, a lot of times, it's difficult to decide which operator to use. In this post I will share some of the mental process I go through when picking the most appropriate operator.

In case you are not familiar with them[1]:

  1. Signal<T, E: Error>
  2. SignalProducer<T, E: Error>
  3. MutableProperty<T>/Property<T>
  4. Action<Input, Output, E: Error>
1. Do I need state? 🤔

In some cases, we need access to the current value at any point we wish. For those situations we can use a Property<T>/MutableProperty<T>:

self.textProperty = MutableProperty("")

Which can then be modified via binds (<~):

viewModel.textProperty <~ textField.reactive.continuousTextValues

Finally, when you are ready to use the value, from your ViewModel:

let value = self.textProperty.value
// use the value (e.g. send it to your server)

On the other hand, if your ViewModel is read-only, you should probably use a Property<T>. Imagine your ViewModel is exposing the user's current location:

// `locationManager.coordinates` is a `Signal< CLLocation, NoError>`
// `self.coordinates` is a Property<CLLocation?>
self.coordinates = Property(initial: nil, then: locationManager.coordinates)

It's very tempting to just expose a MutableProperty<T>, but then you defeat the purpose of the ViewModel being read-only, since the consumer (typically the View layer) can now change the value as well.

The same way Swift's makes you think about using a let instead of a var, you will go through the same dilemma with ReactiveSwift.

2. Hot and Cold Semantics đŸ”Ĩ❄ī¸

ReactiveSwift is fairly honest when it comes to these concepts, to the extent of providing two separate operators to represent them (Signal<T, E: Error> and SignalProducer<T, E: Error> respectively). This is a design decision that set apart ReactiveSwift from other libraries (for better or worst). In one hand it forces the user to consciously think about his/her app flow semantics, on the other hand, it can be considered less pragmatic by some.

Cold ❄ī¸ semantics means that at call site, you want to start and observe the outcome of some work:

  1. Start network request.
  2. Fetch Data from Database.
  3. Read some file from the bundle.
  4. Parse some JSON.

This initiation and observation of work is not shared. This becomes more obvious when you realize that SignalProducer is a struct.

Hot đŸ”Ĩ semantics means that at call site you want to observe some work, that could potentially been started:

  1. Observing the app lifecycle.
  2. Observing push notifications.
  3. Keeping track of a gesture recogniser.

Any of these can have multiple observers and each one will be aware of the work being done. There is no guarantee that you will observe all the events. If you are observing your app life cycle, you will probably miss the application(_:didFinishLaunchingWithOptions:). Finally a Signal is a class.

There is more to this, but understanding the nature of what you are trying to achieve is imperative when choosing the right operator. For example, using a Signal to initiate a network request will make you fight the framework.

3. The odd one

An Action<Input, Output, E: Error> is a mixed bag. It has cold semantics, in the sense that you can start the work, but it also has hot semantics, since it can be observed by multiple parties, but there is no guarantee that you will observe all events. Typically an Action will be exposed by the ViewModel and bound (via ReactiveCocoa's UIKit bindings) to a UIButton:

/// `RequestDTO` is an entity that will be used when making a network request
/// `FooBarDTO` represents the parsed response when the network request succeeds
/// `toFarBarDTO` is a function that parses a network response into a `FooBarDTO` 
/// `NetworkError` is the error that will be sent when the request fails
self.action = Action<RequestDTO, FooBarDTO, NetworkError> { request in 
   return network.access(with: request).flatMap(toFooBarDTO)

It can then be started:

let myRequest = ...

And observed:

self.action.values.on(value: { fooBar in 
  /// do something with `fooBar` 

self.action.errors.on(value: { error in 
  /// do something with `error`

Don't forget to observe first and then start, so you don't miss any event. 😅

ReactiveSwift provides a rich set of operators that address most situations that you will find when describing your system. Although in some cases you might think there is an operators overlap, there is typically one that stands out in the crowd. đŸŒŗ

Any question or suggestion, leave a comment or hit me up on twitter!

  1. For simplicity sake, I left ValidatingProperty out. ↩ī¸Ž