RxSwift: Transforming Operators
Learn how to leverage transforming operators in RxSwift, in this tutorial taken from our latest book, RxSwift: Reactive Programming With Swift! By Scott Gardner.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
RxSwift: Transforming Operators
20 mins
flatMapLatest
flatMapLatest
is actually a combination of two operators, map
and switchLatest
. You’ll learn about switchLatest
in the “Combining Operators” chapter of the book, but you’re getting a sneak peek here. switchLatest
will produce values from the most recent observable, and unsubscribe from the previous observable.
So, flatMapLatest
“Projects each element of an observable sequence into a new sequence of observable sequences and then transforms an observable sequence of observable sequences into an observable sequence producing values only from the most recent observable sequence.” Wowza! Take a look at the marble diagram of flatMapLatest
.
flatMapLatest
works just like flatMap
to reach into an observable element to access its observable property, it applies a transform and projects the transformed value onto a new sequence for each element of the source observable. Those elements are flattened down into a target observable that will provide elements to the subscriber. What makes flatMapLatest
different is that it will automatically switch to the latest observable and unsubscribe from the the previous one.
In the above marble diagram, O1
is received by flatMapLatest
, it transforms its value
to 10
, projects it onto a new observable for O1
, and flattens it down to the target observable. Just like before. But then flatMapLatest
receives O2
and it does its thing, switching to O2
’s observable because it’s now the latest.
When O1
’s value
changes, flatMapLatest
actually still does the transform (something to be mindful of if your transform is an expensive operation), but then it ignores the result. The process repeats when O3
is received by flatMapLatest
. It then switches to its sequence and ignores the previous one (O2
). The result is that the target observable only receives elements from the latest observable.
Add the following example to your playground, which is a copy/paste of the previous example except for changing flatMap
to flatMapLatest
:
example(of: "flatMapLatest") {
let disposeBag = DisposeBag()
let ryan = Student(score: Variable(80))
let charlotte = Student(score: Variable(90))
let student = PublishSubject<Student>()
student.asObservable()
.flatMapLatest {
$0.score.asObservable()
}
.subscribe(onNext: {
print($0)
})
.addDisposableTo(disposeBag)
student.onNext(ryan)
ryan.score.value = 85
student.onNext(charlotte)
// 1
ryan.score.value = 95
charlotte.score.value = 100
}
Only one thing to point out here that’s different from the previous example of flatMap
:
-
Changing
ryan
’s score here will have no effect. It will not be printed out. This is becauseflatMapLatest
has already switched to the latest observable, forcharlotte
.
--- Example of: flatMapLatest ---
80
85
90
100
So you may be wondering when would you use flatMap
for flatMapLatest
? Probably the most common use case is using flatMapLatest
with networking operations. You will go through examples of this later in the book, but for a simple example, imagine that you’re implementing a type-ahead search. As the user types each letter, s
, w
, i
, f
, t
, you’ll want to execute a new search and ignore results from the previous one. flatMapLatest
is how you do that.
Challenges
Completing challenges helps drive home what you learned in this tutorial. There are starter and finished versions of the challenge in the exercise files download.
Challenge 1: Accept Alpha-Numeric Characters
In the accompanying challenge, you have code necessary to look up a contact based on a 10-digit number entered by the user.
input
.skipWhile { $0 == 0 }
.filter { $0 < 10 }
.take(10)
.toArray()
.subscribe(onNext: {
let phone = phoneNumber(from: $0)
if let contact = contacts[phone] {
print("Dialing \(contact) (\(phone))...")
} else {
print("Contact not found")
}
})
.addDisposableTo(disposeBag)
Your goal for this challenge is to modify this implementation to be able to take letters as well, and convert them to their corresponding number based on a standard phone keypad (abc
is 2, def
is 3
, and so on).
The starter project includes a helper closure to do the conversion:
let convert: (String) -> UInt? = { value in
if let number = UInt(value),
number < 10 {
return number
}
let convert: [String: UInt] = [
"abc": 2, "def": 3, "ghi": 4,
"jkl": 5, "mno": 6, "pqrs": 7,
"tuv": 8, "wxyz": 9
]
var converted: UInt? = nil
convert.keys.forEach {
if $0.contains(value.lowercased()) {
converted = convert[$0]
}
}
return converted
}
And there are closures to format and “dial” the contact if found (really, just print it out):
let format: ([UInt]) -> String = {
var phone = $0.map(String.init).joined()
phone.insert("-", at: phone.index(
phone.startIndex,
offsetBy: 3)
)
phone.insert("-", at: phone.index(
phone.startIndex,
offsetBy: 7)
)
return phone
}
let dial: (String) -> String = {
if let contact = contacts[$0] {
return "Dialing \(contact) (\($0))..."
} else {
return "Contact not found"
}
}
These closures allow you to move the logic out of the subscription, where it really doesn’t belong. So what’s left to do then? You’ll use multiple map
s to perform each transformation along the way. You’ll use skipWhile
to skip 0
s at the beginning.
You’ll also need to handle the optionals returned from convert
. To do so, you can use a handy operator from the RxSwiftExt repo created by fellow author Marin: unwrap
. RxSwiftExt includes useful operators that are not part of the core RxSwift library. The unwrap
operator replaces the need to do this:
Observable.of(1, 2, nil, 3)
.flatMap { $0 == nil ? Observable.empty() : Observable.just($0!) }
.subscribe(onNext: {
print($0)
})
.addDisposableTo(disposeBag)
With unwrap
, you can just do this:
Observable.of(1, 2, nil, 3)
.unwrap()
.subscribe(onNext: {
print($0)
})
.addDisposableTo(disposeBag)
The starter project also includes code to test your solution. Just add your solution right below the comment // Add your code here
.
Where to Go From Here?
You can download the final package from this tutorial here.
If you enjoyed what you learned in this tutorial, why not check out the complete RxSwift book, available on our store?
Here’s a taste of what’s in the book:
- Getting Started: Get an introduction to the reactive programming paradigm, learn the terminology involved and see how to begin using RxSwift in your projects.
- Event Management: Learn how to handle asynchronous event sequences via two key concepts in Rx — Observables and Observers.
- Being Selective: See how to work with various events using concepts such as filtering, transforming, combining, and time operators.
- UI Development: RxSwift makes it easy to work with the UI of your apps using RxCocoa, which provides an integration of both UIKit and Cocoa.
- Intermediate Topics: Level up your RxSwift knowledge with chapters on reactive networking, multi-threading, and error handling.
- Advanced Topics: Round out your RxSwift education by learning about MVVM app architecture, scene-based navigation, and exposing data via services.
- And much, much more!
By the end of this book, you’ll have hands-on experience solving common issues in a reactive paradigm — and you’ll be well on your way to coming up with your own Rx patterns and solutions!
To celebrate the launch of the book, it’s currently on sale for $44.99 - that’s a $10 discount off the cover price! But don’t wait too long, as this deal is only on until Friday, April 7.
If you have any questions or comments on this tutorial, feel free to join the discussion below!