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.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

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:

  1. Changing ryan’s score here will have no effect. It will not be printed out. This is because flatMapLatest has already switched to the latest observable, for charlotte.
--- 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 maps to perform each transformation along the way. You’ll use skipWhile to skip 0s 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!