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:
- 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.
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.
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
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
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
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.
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:
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.