Rx for the Ultimate Beginner – Part 1 (Swift)

Rx (aka ReactiveX) is a library which provides all the utilities you need to write code using the functional reactive programming (FRP) paradigm. If you have no idea what FRP is, don’t worry, this post has been written for you!. We will discuss what FRP is, why it is useful and what you can achieve when you utilize it correctly. Since Rx in its entirety is a huge library, it is not possible to cover all the tools it provides in a single post. Therefore this guide will expand into multiple parts, building on the previous post

We are going to use Rx in Swift 4, on the iOS platform. If you are looking for the Kotlin version which runs on Android, please refer to this mirror post. All the consecutive posts will follow the same format.

  1. Part 1 – Introduction
  2. Part 2 – Observable Contract
  3. Part 3 – Creating Observables
  4. Part 4 – flatMap Operator

As always, you can find a working project at the bottom of this page.

What is FRP?

If you have heard about FRP before, chances are you were shown a description made up of   of weird terminology. In this post, we are approaching the topic on a pragmatic level instead of an academic one.

FRP is a paradigm used to write highly concurrent code in a composable way. When used correctly, it solves the problem commonly called callback hell when you are working on asynchronous logic.

In FRP, you describe your program as a series of transformations. At a pseudo code level, instead of writing code like this:

DoTheFirstThing()
DoTheSecondThing()
loop for something
DoTheLastThing()

Your code will look like this:

initialData = FirstThingToDo()
transformedData = FirstTransformation(initialData)
loopedData = FirstLoop(transformedData)
extractedData = FirstExtraction(loopedData)

which is more functional at its core. The catch here is that, the second pseudo-code above will be maximally asynchronous, in other words it will not block the context you are working while it is doing its job.

To be able to achieve this, instead of directly transforming data types named A, BC etc to each other, instead you work with envelope objects which encapsulate your A, BC  like Observable<A>, Observable<B>, Observable<C>.

Observables are recipes of type transformations. They make no assumptions about their encapsulated types, the amount of data they will process or the time it will take for the data to arrive for processing. So in a sense, they are array-like objects where each element is both separated by time and space. Regular arrays only arrange their elements spatially, but they require all of them to be ready at the time when they are defined. Thus, you literally can’t have an array of infinite size, where each element is separated with (for example) 1 second long intervals. On the other hand, when nested in an observable, your object collection becomes an abstract entity, where each element is transformed according to your description, as it arrives. The source of the data stream can be disk, network or memory. They will behave exactly the same.

If you are already an experienced Swift user, chances are you know how to use array operators like array.filter() and array.map(). These operators receive anonymous blocks to decide how to filter or convert each element. Observables work the same way. Actually, filter() and map() already exist in Rx. In order to achieve its asynchronicity, Rx demands you to provide a final command to start executing its potentially infinite transformation pipeline, in which you are notified with the final result for each element via the provided callback.

Installation

Add the below lines into your podfile and execute pod install. If you are looking for an earlier version, please refer to RxSwift repo.

# Podfile
use_frameworks!

target 'YOUR_TARGET_NAME' do
    pod 'RxSwift',    '~> 4.0'
    pod 'RxCocoa',    '~> 4.0'
end

# RxTest and RxBlocking make the most sense in the context of unit/integration tests
target 'YOUR_TESTING_TARGET' do
    pod 'RxBlocking', '~> 4.0'
    pod 'RxTest',     '~> 4.0'
end

Hello Observable

In order to visualize how to convert a native array processing code into its asynchronous Rx counterpart, we will start with the most basic example:

func arrayExample() {
    let myArray = [1, 2, 3]
    let transformation = myArray.map { e -> Int in
        // print("Map: ", e)
        return e * 2
    }
    let filtered = transformation.filter { e -> Bool in
        // print("Filter: ", e)
        return e > 2
    }
    
    filtered.forEach { e in
        print("Result: ", e)
    }
    print("Done!")
}

// Output is
// Result: 4
// Result: 6
// Done!

Here is how the above code transforms into Rx:

func observableExample() {
    let myObservable = Observable.of(1, 2, 3) // or Observable.from([1, 2, 3])
    let transformation = myObservable.map { e -> Int in
        // print("Map: ", e)
        return e * 2
    }
    let filtered = transformation.filter { e -> Bool in
        // print("Filter: ", e)
        return e > 2
    }
    
    _ = filtered.subscribe(
        onNext: { e in
            print("Result: ", e)
        },
        onError: { err in
            print(err)
        },
        onCompleted: {
            print("Done!")
        },
        onDisposed: {
            print("Garbage collected!")
        }
    )
}

// Output is
// Result: 4
// Result: 6
// Done!
// Garbage collected!

At first glance, it looks like we have increased the amount of boilerplate for such a simple task. Before we explain the reasoning, let’s take a look at what we did.

We created an observable with 3 elements, all immediately defined. We told our observable to multiply all the values it emits by 2. We then discarded the ones equal to or less than 2. Then to initiate the transformations, we subscribed to the observable and told it to print all the results one by one, or the errors if any occur. Optionally, we told it to notify when the transformations complete and all temporary data is deleted.

These elements are called emissions because they are not limited to definitions in your code as hard coded literals. They can arrive from an API call or user input at different times. We will show you how to create observables which emit values at different times in the future.

To be able to understand what’s happening behind the scenes, please uncomment the print(e) lines in the map() and filter() blocks on both examples. First example should print something:

Map: 1
Map: 2
Map: 3
Filter: 2
Filter: 4
Filter: 6
Result: 4
Result: 6
Done!

As you can see, it didn’t start filtering before multiplying all the values with 2. Once 2, 4 and 6 is ready, it discarded the 2 and showed us the results. Now, let’s see what happens when we execute the observable example:

Map: 1
Filter: 2
Map: 2
Filter: 4
Result: 4
Map: 3
Filter: 6
Result: 6
Done!

It may look complicated but it actually isn’t. The first thing you should notice is that, at least the order of the results is still preserved. Remember that we are both printing the transformation and the filtering – since our first element is 1, it is multiplied by 2 as usual. Then, 2 is printed again which tells us that our observable decided to filter it instead of multiplying the next element by 2. If we used a longer  list of numbers, the order of the mapping and filtering would also change on our each run. This happens because none of the elements waits for its neighbours’ operations before continuing with its own transformations, giving us the opportunity to process multiple elements of data independently as long as they share the same type.

What is the benefit?

In most cases, when you have time/space ordered data, what you want to achieve is doing something with incoming data as it arrives and acting accordingly without worrying about when it arrives. The input can be a UI input such as touch up events, results of an API call or rows of a database table. Similarly, the result can be an UI action, another API call or a database write.

Normally, you cannot store a user touch or a response to an API call before they happen, so the first example cannot be converted to asynchronous code without destroying its outlined logic. On the other hand, Observables can pack the incoming values as  they arrive to apply the recipes describing the transformations with a guaranteed order of results, In other words, the pseudo-code below can translate directly to Rx just like an array transformation.

apiCallResultOfEachButtonClick = myView.touches.map {
    return apiCall()
}

We will cover these kinds of more advanced topics in the next posts.

Working Code

You can find the example repository here.

References