Transforming operators: map, flatMap and flatMapLatest

RxSwift offers a range of operators that provide immense control over how you want to work with the data and events in your application.

Transforming operators are used when you want to model the data coming from a sequence to suit your requirements; in other words, prepare the data to match the requirements of a subscriber.

For example, to transform elements emitted from an Observable sequence, you use the map operator; map will transform each element as it is emitted from the source Observable sequence. This is similar to the map(_:) method in the standard Swift library, except that this map works with Observable sequence elements. A marble diagram representation of the map operator will look like this:


map(_:) in action

We will go through an example of using map in the playground. We will need our helper function, that is, executeProcedure(with description:String, procedure: () -> Void). This function takes two params: a string description and a procedure closure . It then prints the description, followed by the execution of the procedure.

public func executeProcedure(for description:String, procedure: () -> Void){
print("Procedure executed for:", description)
procedure()
}

We will now create an Observable sequence of integers using the of operator:

executeProcedure(for: "map") { 
Observable.of(10, 20, 30)
}

Then, we will use map operator to multiply each integer element by itself:

Observable.of(10, 20, 30)
.map{ $0 * $0 }

Then, we will subscribe onNext to print each transformed element:

Observable.of(10, 20, 30)
.map{ $0 * $0 }
.subscribe(onNext:{
print($0)
})

After that, we call dispose() on the return value from subscribe(onNext:). Remember that subscriptions return a Disposable. The overall code for the function becomes this:

executeProcedure(for: "map") { 
Observable.of(10, 20, 30)
.map{ $0 * $0 }
.subscribe(onNext:{
print($0)
})
.dispose()
}

This prints the expected result, which you can check in the console:


An Observable sequence can itself have Observable properties that you want to subscribe to; flatMap can be used to reach into an Observable sequence to work with its Observable sequences. In the following example, 01, 02, 03 are Observable sequences; flatMap will flatten emissions from each of those sequences into a single sequence, applying a transform to each element:

There is also a flatMapLatest operator. This is the one that you want to know about since you will use this operator a lot.

The difference between flatMap and flatMapLatest is that flatMapLatest will only produce elements from the most recent Observable sequence, so as the Observable sequence emits new Observable sequence, flatMapLatest switches to that new Observable sequence and ignores emissions from the previous sequence.

In the example shown in the following diagram, when the 02 sequence emits the element “2”, 01 will now be ignored because 02 is the latest sequence. As a result, when 01 emits element “3”, it is ignored. Similarly, when 03 starts emitting, starting with element “4”, it becomes the active sequence and 02 is ignored. So, when 02 emits element “5”, it is ignored:

flatMapLatest is quite commonly used with asynchronous operations such as networking.


flatMap and flatMapLatest in action

We read about three transforming operators and saw the map operator in action.Now, we will work with flatMap and flatMapLatest and see how we can implement these operators in a code base. Let’s start with flatMap.

First, we will declare a GamePlayer struct with a playerScore property that is a Variable event of the Int type. We will also declare a disposeBag, as follows:

executeProcedure(for: "flatMap and flatMapLatest") {

struct GamePlayer {
let playerScore: Variable<Int>
}
let disposeBag = DisposeBag()
}

Next, let’s declare a couple of GamePlayer instances, alex and gemma, and a current player that is also a Variable, with an initial value of alex:

let alex = GamePlayer(playerScore: Variable(70))
let gemma = GamePlayer(playerScore: Variable(85))

var currentPlayer = Variable(alex)

We should point out that currentPlayer is of the Variable<Player> type; you can see this by Option + clicking on the currentPlayer instance, as follows:

The dataType of the currentPlayer variable can be inferred from its initial value. So, currentPlayer is an Observable of a GamePlayer, which has a playerScore Observable property. We want to subscribe to the playerScore of the currentplayer. Remember that we will need to first call asObservable() to work with the observable subject value of Variable, because a Variable is a wrapper around BehaviorSubject. A moment ago, we mentioned that a flatMap and flatMapLatest allow us to reach into an Observable to access its observable properties. Let’s see this in action; we will use flatMap first. We will reach into the element, a GamePlayer instance, access its playerScore Variable, and get its Observable subject value:

currentPlayer.asObservable()
.flatMap{ $0.playerScore.asObservable() }

Now, subscribe to that observable and print it out:

currentPlayer.asObservable()
.flatMap{ $0.playerScore.asObservable() }
.subscribe(onNext:{
print($0)
})

At the end, add the subscription to the disposeBag:

currentPlayer.asObservable()
.flatMap{ $0.playerScore.asObservable() }
.subscribe(onNext:{
print($0)
})
.disposed(by: disposeBag)

As Variable wraps a BehaviorSubject that replays its most recent or initial element to new subscribers, that value is printed out:

We will update the value of the playerScore of currentPlayer:

currentPlayer.value.playerScore.value = 90

Then, that value is printed out:

It is important to realize that what we are specifically subscribed to the score of alex because the value of currentPlayer is currently alex. So we can also just add a new value to alex.playerScore:

alex.playerScore.value = 95

Our subscription will print out that value, as follows:

Let’s add gemma to our currentPlayer instance and monitor the changes:

currentPlayer.value = gemma

You will note that the subscription will now print the values accordingly; that is, gemma’s score will be printed:

Can you guess what happened to our subscription to alex’s playerScore? Let’s find out; we will add a new value to alex.playerScore.value directly:

alex.playerScore.value = 96

Then, you will note that it is also printed in the console:

This is because flatMap does not unsubscribe from the previous sequence. So be watchful while using flatMap. If you use flatMap, you could be keeping a bunch of subscriptions around that you don’t need anymore, and those nextEvent handlers will do things that you don’t mean to do or you no longer want to do.

On the other hand, flatMapLatest will unsubscribe from the previous sequence, subscribe to the new one, and only produce values from the most recent sequence. Let’s take a look at the code that we have written for flapMap till now:

executeProcedure(for: "flatMap and flatMapLatest") {

struct GamePlayer {
let playerScore: Variable<Int>
}
let disposeBag = DisposeBag()

let alex = GamePlayer(playerScore: Variable(70))
let gemma = GamePlayer(playerScore: Variable(85))

var currentPlayer = Variable(alex)

currentPlayer.asObservable()
.flatMap{ $0.playerScore.asObservable() }
.subscribe(onNext:{
print($0)
})
.disposed(by: disposeBag)

currentPlayer.value.playerScore.value = 90
alex.playerScore.value = 95

currentPlayer.value = gemma

alex.playerScore.value = 96
}

The output of the code is this:

Now, let’s change flatMap to flatMapLatest in the preceding code:

executeProcedure(for: "flatMap and flatMapLatest") {
struct GamePlayer {
let playerScore: Variable<Int>
}
let disposeBag = DisposeBag()

let alex = GamePlayer(playerScore: Variable(70))
let gemma = GamePlayer(playerScore: Variable(85))

var currentPlayer = Variable(alex)

currentPlayer.asObservable()
.flatMapLatest{ $0.playerScore.asObservable() }
.subscribe(onNext:{
print($0)
})
.disposed(by: disposeBag)

currentPlayer.value.playerScore.value = 90
alex.playerScore.value = 95

currentPlayer.value = gemma

alex.playerScore.value = 96
}

Note that we no longer receive new events on alex. So the 96 we added to alex.playerScore.value is not printed:

There are a few more transforming operators in RxSwift, and I will cover them in my next blogs.


For other updates you can follow me on Twitter on my twitter handle @NavRudraSambyal

To read in depth about Rx concepts and RxSwift you can follow the link to my book Reactive programming in Swift 4

Thanks for reading, please share it if you found it useful :)