Conquering ReactiveSwift: Action (Part 6)

Susmita Horrow
Fueled Engineering
Published in
6 min readApr 5, 2018

Welcome to Part 6 of my Conquering ReactiveSwift series. In the previous article, we learned about the Property and MutableProperty primitives. In this article, we will discuss about another primitive under the Source category which is the Action.

Definition

An Action represents a repeatable work that can be started later, similar to a SignalProducer but with higher order features like:

  1. The ability to enforce serial execution
  2. The ability to provide varying inputs
  3. The ability to enable execution conditionally
  4. The ability to check if execution is already in-progress

Let’s consider the following problem to better understand what is an Action is and how to use it.

Print a message of time elapsed on every N seconds interval for next N * 10 seconds

When learning about a SignalProducer, we saw that if we wanted to defer the start of a Signal, we need to wrap the work inside a SignalProducer. At that time, we had defined a SignalProducer which emits an integer in every 5 seconds for next 50 seconds. But, what would we do when we want to provide different time intervals each time we started the SignalProducer?

One way is to define a closure which takes an input and returns a SignalProducer like this:

So that we can use the closure like this:

That was easy. Right?

Now we want to make these two operations mutually exclusive. In other words, we want to make sure that signalProducer2 doesn’t start unless signalProducer1 has completed. To implement this behaviour, we have to use an Action.

An action is defined as a unit of repeatable work which gets executed with a varying input.

The core of Action is the execute closure, which encapsulates the unit of repeatable work in the form of a SignalProducer. When an Action is invoked via apply(), the execute closure is executed, creating a SignalProducer. We then need to call start on the resulting SignalProducer. Once invoked, it may send zero or more values and terminate with/without an error at some point, just like you’d expect. While the execution is in-progress, the Action is disabled, I.e it cannot be started unless the current execution has finished, as the figure below describes:

An Action has the following useful properties:

  • values: A signal of all values generated from all invocations of the Action
  • error: A signal of all errors generated from all invocations of the Action
  • isExecuting: A property of type Property<Bool> denoting whether the action is currently executing
  • isEnabled: A property of type Property<Bool> denoting whether the action is currently enabled

An Action is a generic class which takes three generic parameters. The class definition looks like this:

public final class Action<Input, Output, Error: Swift.Error> 
  • Input: Denotes the type of external input to be supplied by apply().
  • Output: Denotes the type of values emitted by the signal of the Action.
  • Error: Denotes the type of error sent by the signal

For our example, we need an Input of type Int, an Output of type Int, and an Error of type NoError.

Now we are ready to define our action. An Action is typically initialized with a closure of the following type:

(Input) -> SignalProducer<Output, Error>

So we need a closure of type (Int) -> SignalProducer<Int, NoError>. Here, we can reuse the closure we had earlier defined. So this is how we will define our action:

Now let’s define an observer for this Action’s Signal, accessible by way of the Action’s values property:

Then we are able to call apply on the Action with varying input values:

  1. When the first apply statement gets executed, the logs are printed after 1 second.
  2. The second apply statement is ignored as the first one is still executing.
  3. The third apply statement gets executed after 12 seconds, because by this time the first one has already finished execution. In this case the logs are printed every 3 seconds.

Here is the complete code:

Let’s recap what we have learned:

  1. We define a closure which returns a SignalProducer
  2. We create an Action using that closure
  3. We then observe the Action for activity
  4. We apply start on the Action to kick things off!

In the example above, we defined an Action which is dependent only on the external input, provided via theapply method. However, an Action can also be modeled such that it can produce output depending on its internal state, dictated by a Property as well as the external input. In this case, the execute closure can access both the current state of the Action (represented by the property) and external input supplied by the apply method.

To understand this, let us take an example:

Suppose we are building a blogging application, where we have a requirement that title should have at least 10 characters. We need to design a validator which will accept the minimum character length and will perform length check on the current text.

Let’s proceed step by step:

1. Define the closure

For this scenario, we need a closure of the following type:

(State.Value, Input) -> SignalProducer<Output, Error>

The first parameter represents the current state, while the second parameter represents the external input received by theapply method. In our example, the first parameter will be the text input provided by the user, and the second parameter will be the minimum character length.

Let’s define this closure. This closure returns a SignalProducer which will emit a boolean value indicating whether the character count exceeds the minimum character length:

2. Define a Property

For this case, we need to model the user input as a Property of type String. A Property is an observable box which emits its changed values and the values can be dictated either by a Signal or a SignalProducer. If we were using ReacticeCocoa, we could use the signal textField.reactive.continuousTextValues which represents the text entered by the user. However, for this example, let’s simulate the text input through a Signal which will emit one character every second. Consider the following function textSignalGenerator that accepts a String and emits each character one at a time every second:

Now we can use this Signal and define a Property:

3. Define an Action

We have the both the property and closure ready. Now let’s define the action which would perform the validation:

4. Define the Observer

As we have discussed before, we can observe the result calculated by an Action through thevalues property which provides the underlying Signal. Let’s log the value returned by titleLengthChecker in the observer block:

5. Apply the Action

Now let start the action. In the code below, we start the action every second for the total length of title. We call apply on titleLengthChecker with value 10 and then call start.

We can reuse the same lengthCheckerSignalProducer closure and define a new action if we were to run validation for asubtitle which may have different requirements for character length.

Another important characteristics of an Action is that it can be conditionally enabled. To understand this, let’s extend the previous example a bit. Suppose we want to run the validation, but only if the character count is greater than 5. We can define this as follows:

Conclusion

An Action can also be practically useful when implementing network calls. By wrapping a network call inside an Action, we can ensure that each invocation of the network call is mutually exclusive, and also conditionally enable and disable the call. We can furthermore observe properties like isExecuting and isEnabled and then manage loading states of the view.

So this is all about Action. You can find the sample code here. In the next article, we will discuss how to limit the scope of observation.

Thank you for reading. 😄

--

--