RxSwift: Transforming Operators in Practice

Learn how to work with transforming operators in RxSwift, in the context of a real app, in this tutorial taken from our latest book, RxSwift: Reactive Programming With Swift! By Marin Todorov.

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

Using flatMap to Wait for a Web Response

In the previous tutorial, you learned that flatMap flattens out observable sequences. One of the common applications of flatMap is to add some asynchronicity to a transformation chain. Let’s see how that works.

When you chain several transformations, that work happens synchronously. That is to say, all transformation operators immediately process each other’s output:

When you insert a flatMap in between, you can achieve different effects:

  • You can flatten observables that instantly emit elements and complete, such as the Observable instances you create out of arrays of strings or numbers.
  • You can flatten observables that perform some asynchronous work and effectively “wait” for the observable to complete, and only then let the rest of the chain continue working.

What you need to do in your GitFeed code is something like this:

To do that, append the following code to the operator chain that you have so far:

.flatMap { request -> Observable<(HTTPURLResponse, Data)> in
  return URLSession.shared.rx.response(request: request)
}

You use the RxCocoa response(request:) method on the shared URLSession object. That method returns an Observable, which completes whenever your app receives the full response from the web server. You will learn more about the RxCocoa rx extensions and how to extend Foundation and UIKit classes yourself in the full RxSwift book.

In the code you just wrote, flatMap allows you to send the web request and receive a response without the need of protocols and delegates. How cool is that? Freely mixing map and flatMap transformations (as above) enables the kind of linear yet asynchronous code you hopefully are starting to appreciate.

Finally, to allow more subscriptions to the result of the web request, chain one last operator. You will use shareReplay(1) to share the observable and keep in a buffer the last emitted event:

.shareReplay(1)

Here you’re using shareReplay(_). Let’s have a look why.

share vs. shareReplay

URLSession.rx.response(request:) sends your request to the server and upon receiving the response emits once a .next event with the returned data, and then completes.

In this situation, if the observable completes and then you subscribe to it again, that will create a new subscription and will fire another identical request to the server.

To prevent situations like this, you use shareReplay(_). This operator keeps a buffer of the last X emitted elements and feeds them to any newly subscribed observer. Therefore if your request has completed and a new observer subscribes to the shared sequence (via shareReplay(_)) it will immediately receive the response from the server that’s being kept in the buffer.

The rule of thumb for using shareReplay(_) is to use it on any sequences you expect to complete – this way you prevent the observable from being re-created. You can also use this if you’d like observers to automatically receive the last X emitted events.

Transforming the Response

It will probably not come as a surprise that along with all the map transforms you did before sending the web request, you will need to do some more after you receive its response.

If you think about it, the URLSession class gives you back a Data object, and this is not an object you can work with right away. You need to transform it to JSON and then to a native object you can safely use in your code.

You’ll now create a subscription to the response observable that converts the response data into objects. Just after that last piece of code you wrote, add the following code on a new line:

response
  .filter { response, _ in
    return 200..<300 ~= response.statusCode
  }

With the filter operator above, you easily discard all error response codes. Your filter will only let through responses having a status code between 200 and 300, which is all the success status codes.

Note 1: Interested in the HTTP response codes list? Check out this article on Wikipedia: https://en.wikipedia.org/wiki/List_of_HTTP_status_codes
Note 2: What about this pesky, built-in ~= operator? It’s one of the lesser-known Swift operators, and when used with a range on its left side, checks if the range includes the value on its right side.
Note 3: You’re going to ignore the non-successful status codes, instead of having your observable send an error event. This is a stylistic choice meant to keep the code simple for now.

The data you receive will generally be a JSON-encoded server response containing a list of event objects. As your next task, you will try transforming the response data to an array of dictionaries.

Append another map to the last operator chain:

.map { _, data -> [[String: Any]] in
  guard let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []),
    let result = jsonObject as? [[String: Any]] else {
      return []
  }
  return result
}

Let’s deconstruct this piece of code:

  • Unlike what you’ve done previously, you discard the response object and take only the response data.
  • You aid the compiler by letting it know you will return an Array. This is what an array of JSON objects looks like.
  • You proceed to use JSONSerialization as usual to try to decode the response data and return the result.
  • In case JSONSerialization fails, you return an empty array.

It’s really cool how RxSwift forces you to encapsulate these discrete pieces of work by using operators. And as an added benefit, you are always guaranteed to have the input and output types checked at compile time.

You are almost finished processing the API response. There’s a couple of things left to do before updating the UI. First, you need to filter out any responses that do not contain any event objects. Append to the chain:

.filter { objects in
  return objects.count > 0
}

This will discard any error responses or any responses that do not contain new events since you last checked. You’ll implement fetching only new events later in the tutorial, but you can account for this now and help out your future self. :]

As a final transformation, you will convert the list of JSON objects to a collection of Event objects. Open Event.swift from the starter project and you will see that the class already includes the following:

  • A handy init that takes a JSON object as a parameter
  • A dynamic property named dictionary that exports the event as a JSON object

That’s about everything you need this data entity class to do.

Switch back to ActivityController.swift and append this to the last operator chain inside fetchEvents(repo:):

.map { objects in
  return objects.map(Event.init)
}

This final map transformation takes in a [[String: Any]] parameter and outputs an [Event] result. It does that by calling map on the array itself and transforming its elements one-by-one.

Bam! map just went meta! You’re doing a map inside of a map. :]

I hope you noticed the difference between the two maps. One is a method on an Observable> instance and is acting asynchronously on each emitted element. The second map is a method on an Array; this map synchronously iterates over the array elements and converts them using Event.init.

Finally, it’s time to wrap up this seemingly endless chain of transformations and get to updating the UI. To simplify the code, you will write the UI code in a separate method. For now, simply append this code to the final operator chain:

.subscribe(onNext: { [weak self] newEvents in
  self?.processEvents(newEvents)
})
.addDisposableTo(bag)