Conquering ReactiveSwift: Primitives (Part 2)

Susmita Horrow
Fueled Engineering
Published in
5 min readJan 2, 2018

In Part One of our Conquering ReactiveSwift Series, we analyzed the difference between the imperative way and the functional reactive way of solving a problem. In this article, we will dive into the realm of ReactiveSwift, starting with the various primitives(basic building blocks) of ReactiveSwift.

Let’s get started

The authors of ReactiveSwift describe it as follows:

ReactiveSwift offers composable, declarative and flexible primitives that are built around the grand concept of streams of values over time.

ReactiveSwift comes with many powerful primitives which enables you to write code in a functional reactive way. The easiest way to learn these primitives is by grouping them into different categories according to their roles. Please refer to the image below.

Source

As we discussed in the previous article, in reactive programming, we model our system against a series of changes over time, rather than against a state at a particular point in time. The primitives under the “Source” category, are responsible for the generation and propagation of those changes. We have the following primitives under this category: Event, Signal, Signal Producer, Action.

1. Event

An Event denotes something that happened. It is the basic transfer unit of a stream. There are four types of events:

  • Value: An event with valid information
  • Failed: An event denoting error
  • Completed: An event denoting end the stream. No more event can be emitted after this event.
  • Interrupted: Denotes event production is interrupted.

2. Signal

A Signal is a unidirectional stream of events. One can access each event of a signal by subscribing via observers. Observing a signal does not inject any side effect to the signal. All the events are sent at once to all the observers of a given signal.

3. Signal Producer

As the name suggests, a SignalProducer is something that produces a signal. It encapsulates the work which can be started later (and can be repeated too). In other words, a SignalProducer producer encapsulates a deferred and repeatable work. When it is started, a new signal is created, which emits values as the result of an invocation of the encapsulated work. Therefore, a SignalProducer is said to be cold as it needs to be started, whereas a Signal is warm.

4. Action

Action too is used in the context of deferred and repeatable work. It encapsulates the work of a SignalProducer. We have exercise better control over an Action rather than a Signal Producer. For example, we can control the output by sending different input values. It can be enabled or disabled. Its state can be controlled reactively through a Property(discussed below). When being invoked with an input, an Action applies the input and the latest state to the predefined work, and pushes the output to the observers.

5. Property

A Property is an observable box which stores a value and notifies observers about future changes to that value. It has getters for producer and signal which emit its values that can be observed. A Property can be initialized either with an initial value and a Signal, a SignalProducer or directly via another Property.

The following image illustrates how the various primitives are related to each other.

Consumer

The primitives under this category listen and act upon the events emitted by source components. We have following primitives under this category: Observer, MutableProperty.

1. Observer

An Observer encapsulates the work to be performed in response to the events emitted. It is a simple wrapper around a closure which takes Event as an input.

2. MutableProperty

A MutableProperty is an observable box which stores a value just like a Property. It also has getters for producer and signal which emit its values that can be observed. However, unlike a Property, it can be mutated directly. It conforms to the protocol BindingTargetProvider, so it can update its value with the values received by either by a Signal, a SignalProducer, or a Property through <~ (the binding operator).

Operators

Operators specify the way the source is being consumed by the consumer. ReactiveSwift comes with a rich set of operator for performing side effects, transformation, combine, flatten etc. These will be discussed in subsequent articles.

Scope

Primitives under this category determine the lifetime of the interaction between source and consumer. We have Disposable and Lifetime under this category.

1. Disposable

A Disposable is a mechanism for memory management and cancellation. Observing a Signal returns a disposable. Disposing the disposable prevents the observer from receiving any future events from that signal. However, this does not affect the signal. On starting a SignalProducer, we get a disposable which can be used to cancel the work that has been started.

2. Lifetime

Lifetime represents the lifetime of an object. This primitive is useful when an observation might outlive the observer. For example, we want to observe a notification as long as an UI component is on the screen.

Putting the pieces together

Now let’s understand how to use these primitives to get our job done. Let’s take an example.

Enable a button when the character count of textfield’s text is greater than 10.

Step 1: Defining a signal

let signal = textField.reactive.continuousTextValues

Here, the text typed in the textfield is expressed through the signal textField.reactive.continuousTextValues. It is a part of ReactiveCocoa, not of ReactiveSwift. ReactiveCocoa wraps various aspects of Cocoa frameworks with the declarative ReactiveSwift primitives.

Step 2: Transforming a signal

The signal created in the first step emits optional string. We have to transform it to emit boolean values via operator map .

let transformedSignal = signal
.map { text in
text ?? "" }
.map { text in
text.characters.count > 10
}

Step 3: Observing Signal

Next we will observe the signal and perform action, i.e setting isEnabled of the button. Here, we create a signal of type <Bool, NoError> which means it can consume the signal/signal producer of type <Bool, NoError>. Then we start observing a signal via observe primitive.

let observer = Signal<Bool, NoError>.Observer(value: { value in
button.isEnabled = value
})
let disposable = transformedSignal.observe(observer)

Step 4: Stop observing a signal

observe primitive returns a Disposable instance, which can be used to stop observing a signal.

disposable?.dispose()

It is a best practice to dispose all the observers in the deinit of a class.

The whole thing…….

//Defining consumerlet observer = Signal<Bool, NoError>.Observer(value: { value in
button.isEnabled = value
})
//Defining sourcelet signal = textField.reactive.continuousTextValues
let transformedSignal = signal
.map { text in
text ?? "" }
.map { text in
text.characters.count > 10
}
//Consuming signallet disposable = transformedSignal.observe(observer)//Limit Scope
disposable?.dispose()

References

You can get more insights on reactive components in the following article:

Conclusion

In this article, we covered different components of ReactiveSwift and how we can use them to write code in functional reactive way. You can find sample code here. In the next article, we will discuss how to create and observe a signal.

Thank you for reading 😄.

--

--