Combining parallel network requests with RxSwift

Alan Steiman
Jul 13 · 4 min read
Image for post
Image for post

Introduction

Concurrency is a concept that every mobile developer should know. It is the notion of multiple things happening at the same time.

The key is to divide our program into small tasks that can run in parallel, and the final result shouldn’t be affected by the order of the task completion, meaning if task A finishes before task B, the outcome should be the same as if task B finishes before task A.

In the iOS ecosystem, there are several tools to achieve that:

  • NSOperation
  • NSThread
  • Grand Central Dispatcher
  • Reactive Programming frameworks like RxSwift, ReactiveSwift, Combine, etc

When dealing with network requests, the recommended approach is to have an asynchronous task (the thread initiated the task won’t wait until the task is completed to continue executing other tasks) in a background thread (because the main thread should be free for UI updates only). This will guarantee a smooth, freeze-free user experience, that allows the user to continue using the app while the network request eventually finishes some time in the future.

Parallel execution improves the overall speed of the app, if task A takes 2 seconds and task B takes 3 seconds, the 2 tasks running in parallel would take 3 seconds, whereas running in serial (one after the other) would take 5 seconds.

Real-world use case

In the classifieds company I worked, there was a screen on the app to display the search results using a UITableView, and the Business wanted to display Featured (premium/paid) results on top of the regular/free results, a practice widely used in the e-commerce and classified world.

The backend had 2 different endpoints to be consumed, one for regular results, one for premium results, and both require the same parameters. The discussion of whether or not the endpoints should be merged into one is for another post.

In order to maximize the premium results exposure, and provide a better user experience, it was decided to execute both network requests at the same time, and wait until both finish before updating the UI. Meaning if request A takes 500ms and request B takes 3 seconds, the user will see a loading screen for 3 seconds.

Implementation

Let’s review the implementation using RxSwift, a popular open-source reactive programming framework, widely used across our app.

We start by creating a method getResults that will receive a dictionary with parameters, and return a Single of Array of Result. The actual implementation is not relevant here, but is basically a network request using URLSession, Alamofire, or any other library, map the response to an array of Result, where Result is just a model representing a single Ad that eventually will be displayed as a row in a list view.

A Single is an Observable that emits either one value or an error. Alternatively, the method could return Observable<Result>, that would be emitting as many values as elements on the network response. But eventually the sequence will be converted to an Array, therefore I think is cleaner to just return Single<[Result]> here.

Then, we create and hold reference to the sequence like so:

This won’t execute the sequences (nor the underlying network requests) yet.

The next step is to create a final sequence combining both network responses.
We will use the Zip operator, that combines the emissions of multiple sequences via a given function, and returns a single item for each combination.

In our example, promotedAdsSequence will emit one value (Array of Result) and regularAdsSequence will also emit one value, therefore the Zip operator will return a sequence that only emits one value.

Since both sequences are of type Single, the Zip operator has to be invoked on the same type.

This is still not firing the requests, in order to do so, we need an Observer to subscribe to finalSequence:

This last snipped is the one triggering the parallel network requests, and the onSuccess block will be executed when both finish successfully. If one fails, the whole operation fails.

Catching errors

But even though the tasks are in parallel and the operation is atomic, the call to get regular ads is the main one, and if the promoted call fails, we can at least show the regular ads instead of an error message.

The solution to this is to catch the error on the promoted sequence and return just an empty error:

Conclusion

It is mandatory for a mobile developer to have a good understanding of threads, sync vs async operations, serial vs concurrent queues, in order to create a smooth user experience, and efficiently use the resources issued by the OS to us.

Reactive programming and frameworks like RxSwift provides the interface to easily manage async operations in a declarative way, manipulating and combining the data through various operators, and keeping in mind the memory management to avoid potential leaks.

Other posts

Flawless iOS

🍏 Community around iOS development, mobile design, and…

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store