Operators are a key component of RxSwift and the ReactiveX API. RxSwift defines dozens of operators that create, filter, or transform the values emitted by observables. In this episode, I show you why operators are an integral component of RxSwift and we take a look at a few examples.

Creating Observables

There are several categories of operators. There is one category of operators you are already familiar with, operators that create observables. The just(), empty(), never(), of(_:), from(_:), interval(_:scheduler:), and timer(_:scheduler:) methods are examples of operators that create observables.

import RxSwift
import Foundation

let just = Observable<Int>.just(1)
let empty = Observable<Int>.empty()
let never = Observable<Int>.never()
let of = Observable<Int>.of(1, 2, 3, 4, 5)
let from = Observable<Int>.from([1, 2, 3, 4, 5])
let interval = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
let timer = Observable<Int>.timer(.seconds(2), period: .seconds(3), scheduler: MainScheduler.instance)

Transforming Operators

A category of operators most developers use frequently is the category of transforming operators. Some of these operators feel familiar because they have a counterpart in the Swift standard library. Let's start with the map(_:) operator. We first create an observable of type Int using the of(_:) method. This should look familiar.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 3, 4, 5)

To apply the map(_:) operator to the observable, we invoke the map(_:) method on the observable. The operator accepts a closure in which the values emitted by the observable are transformed. The closure we pass to the map(_:) operator returns the transformed value. In this example, we multiply each value by 2.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 3, 4, 5)

observable.map { (value) -> Int in
    return value * 2
}

To understand what the result of the map(_:) operator is, we subscribe to the resulting observable by invoking the subscribe(onNext:onError:onCompleted:onDisposed:) method. In this example, we are only interested in next events. In the onNext event handler, we print the values emitted by the observable to the console. Because we are working in a playground, there is no need to add the subscription to a dispose bag.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 3, 4, 5)

observable
    .map { (value) -> Int in
        return value * 2
    }.subscribe(onNext: { (value) in
        print(value)
    })

Run the contents of the playground and inspect the output in the console. Each value emitted by the observable is multiplied by 2.

2
4
6
8
10

It's important to understand that the map(_:) operator doesn't modify the source observable. The map(_:) operator creates a new observable and we subscribe to that observable in the playground. Don't worry about this for now. I explain this in more detail in a few minutes.

Another useful operator is compactMap(_:). The behavior of this operator is similar to that of the compactMap(_:) function of the Swift standard library. Let's create an observable of type String that emits the values 1, b, 3, and c.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

I would like to transform the strings emitted by the observable to integers. We could use the map(_:) operator for that purpose, but there's a problem. Not every string can be converted to an integer. This means that the map(_:) operator creates an observable that emits values of type Int?. That isn't what we want.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .map { (value) -> Int? in
        return Int(value)
    }
    .subscribe(onNext: { (value) in
        print(value)
    })

The compactMap(_:) operator is a better fit in this example because it only includes the resulting value if the transformation is successful. Notice that the return value of the closure we pass to the compactMap(_:) operator is of type Int?. Remember that the return value of the closure we passed to the map(_:) operator is also of type Int?. The difference is that the value isn't emitted by the observable if the closure returns nil.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .compactMap { (value) -> Int? in
        return Int(value)
    }
    .subscribe(onNext: { (value) in
        print(value)
    })

If we run the contents of the playground, we see that only the first and the third element of the source observable are emitted by the resulting observable.

Filtering Operators

RxSwift also defines a range of powerful filtering operators. As the name implies, a filtering operator filters the items emitted by a source observable. Let's start with the simplest filtering operator, take(_:). As the name implies, we use the take(_:) operator to take a predefined number of the emitted values. We replace the compactMap(_:) operator with the take(_:) operator. The operator accepts one argument of type Int, the number of emitted values taken from the source observable and emitted by the resulting observable.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .take(2)
    .subscribe(onNext: { (value) in
        print(value)
    })

If we run the contents of the playground and inspect the output in the console, we see that the first and the second string of the source observable are printed to the console.

1
b

The skip(_:) operator can be considered the counterpart of the take(_:) operator. It sometimes happens that you are not interested in the first few items an observable produces. The skip(_:) operator enables you to skip a predefined number of items. In this example, we skip the first and the second item emitted by the source observable by replacing the take(_:) operator with the skip(_:) operator.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .skip(2)
    .subscribe(onNext: { (value) in
        print(value)
    })

The output in the console confirms this. Only the values 3 and c are printed to the console.

3
c

Chaining Operators

Before we continue exploring filtering operators, I would like to talk about chaining operators. Every operator we covered so far returns an observable and that enables us to chain operators. Chaining operators is a key feature of RxSwift and the ReactiveX API. Let's take a look at an example to understand how powerful this concept is.

Let's use the skip(_:) and take(_:) operators as an example. To skip the first value emitted by the source observable, we pass 1 to the skip(_:) operator. Because the skip(_:) operator returns an observable, we can apply the take(_:) operator to that observable. We take the first two values emitted by the observable by passing 2 to the take(_:) operator.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .skip(1)
    .take(2)
    .subscribe(onNext: { (value) in
        print(value)
    })

Run the contents of the playground and inspect the output in the console. Only the values b and 3 are emitted by the resulting observable.

b
3

The order in which operators are applied matters. Let's invert the order of the operators and run the contents of the playground. Only the value b is emitted by the resulting observable.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .take(2)
    .skip(1)
    .subscribe(onNext: { (value) in
        print(value)
    })
b

A marble diagram is helpful to better understand what happens under the hood. In the first example, we apply the skip(_:) operator to the source observable. The resulting observable emits three values. By applying the take(_:) operator, we create an observable that emits the first two values of the resulting observable.

Marble Diagram

In the second example, we apply the take(_:) operator to the source observable. The resulting observable emits two values. By applying the skip(_:) operator, we create an observable that emits a single value.

Marble Diagram

Transforming Observables

Earlier in this episode, I mentioned that operators don't affect the source observable and the values it emits. This rule applies to most operators and it's important that you understand this concept.

The take(_:) operator is applied to the source observable, but it doesn't affect the source observable. It creates a brand new observable that only includes the first and the second value emitted by the source observable. Because the take(_:) operator returns a brand new observable, the skip(_:) operator can be applied to the observable the take(_:) operator creates. It too creates a brand new observable that excludes the first value emitted by the observable the take(_:) operator creates.

Let's subscribe to the source observable to prove that the take(_:) and skip(_:) operators don't affect the source observable.

import RxSwift
import Foundation

let observable = Observable<String>.of("1", "b", "3", "c")

observable
    .take(2)
    .skip(1)
    .subscribe(onNext: { (value) in
        print(value)
    })

observable
    .subscribe(onNext: { (value) in
        print("source", value)
    })

The output in the console confirms that the source observable produces four values. The observable returned by the skip(_:) operator emits one value.

b
source 1
source b
source 3
source c

More Filtering Operators

Another convenient operator I use frequently is the distinctUntilChanged() operator. It filters out duplicate values. To illustrate this, we create an observable that emits a sequence of integers. Notice that the sequence contains duplicates. If we apply the distinctUntilChanged() operator, no two subsequent values are equal.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 2, 2, 3, 4, 3, 5)

observable
    .distinctUntilChanged()
    .subscribe(onNext: { (value) in
        print(value)
    })
1
2
3
4
3
5

The distinctUntilChanged() operator is very useful if an observable drives the user interface of a view controller. It only makes sense to update the user interface if the new value is different from the old value. The distinctUntilChanged() operator makes that trivial.

I won't cover every filtering operator in this episode, but there's one more filtering operator you need to know about, filter(_:). This operator accepts a predicate that defines which values of the source observable are emitted by the resulting observable. The predicate is a closure. It accepts the emitted value and returns true of false. If the return value is true, then the emitted value is included in the resulting observable. If the return value is false, then the emitted value is excluded from the resulting observable. Let's use the filter(_:) operator to only include integers that are divisible by 2.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 2, 2, 3, 4, 3, 5)

observable
    .filter({ (value) -> Bool in
        value % 2  == 0
    })
    .subscribe(onNext: { (value) in
        print(value)
    })

We can optimize the playground by making use of shorthand argument names. It makes the implementation more readable.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 2, 2, 3, 4, 3, 5)

observable
    .filter { $0 % 2 == 0 }
    .subscribe(onNext: { (value) in
        print(value)
    })

Only the values 2 and 4 are included in the resulting observable.

2
2
2
4

To rid the observable of duplicates, we apply the distinctUntilChanged() operator. The resulting observable only emits the values 2 and 4.

import RxSwift
import Foundation

let observable = Observable<Int>.of(1, 2, 2, 2, 3, 4, 3, 5)

observable
    .filter { $0 % 2 == 0 }
    .distinctUntilChanged()
    .subscribe(onNext: { (value) in
        print(value)
    })
2
4

What's Next?

Transforming and filtering operators are the easiest to understand, but there are several other categories of operators. We cover those in the next episode and later in this series. Some operators are quite advanced and they require a bit more knowledge of RxSwift and reactive programming. In the next episode, we continue with combining observables.