This tutorial will demonstrate the usage of Combine for handling networking tasks.

Picture from Storyset on Freepik

Introduction

Most likely, all of us have encountered situations where we’ve had to write code like this:

photoManager.getAccess { accessResult in
switch accessResult {
case .success:
photoManager.selectPhoto { selectionResult in
switch selectionResult {
case .success:
photoManager.uploadPhoto { uploadResult in
...
}
case .failure:
...
}
}

case failure:
photoManager.showAlert {
photoManager.retry {
...
}
}
}
}

This is commonly referred to as the Pyramid of Doom — a code structure that resembles an arrowhead and is considered an anti-pattern resulting from multiple nested closures or structures such as nested conditions or switch statements.

In this material, we will explore how to eliminate nested closures using Combine and thus simplify the code while enhancing its readability and maintainability. Let’s explore how it can be valuable for handling asynchronous tasks, such as networking.

API

As an example, I will be using the chucknorris.io API, which provides hand-curated Chuck Norris facts. After all, who doesn’t enjoy Chuck Norris facts? 😎

API method to retrieve a list of available categories:

https://api.chucknorris.io/jokes/categories

API method to retrieve a random joke from a given category:

https://api.chucknorris.io/jokes/random?category={category}

To get the jokes, we need to load the possible categories (an array of strings) and then make another request to get the joke for a particular category. We have a chain of requests that go one after the other.

Start Project

Firstly, let’s take a quick look at the initial project I created. You can find it on GitHub with a detailed history of changes made to the code.

It includes two models — Category and Joke — both of which have only one field of type String.

There is also a UITableView with UIRefreshControl which has a target action onRefresh to trigger an update. The implementation of table view’s delegate methods moved to the extension.

Additional helpers:

We need to implement updateJokes to fetch data from the remote, map it and assign it to the jokes property.

Adding Combine Publishers

First, we need to load categories. I encapsulated the logic into a function loadCategories.

Let’s take a look at what’s going on in this code.

DataTaskPublisher

Combine ships with built-in support for creating publishers based on URL/ URLRequest similar to the dataTask API that we’ve all used. We can use dataTaskPublisher method of URLSession to get a publisher that wraps a data task for a given URL.

Below we seeOutput and Failure types of DataTaskPublisher

The Output type is a tuple that consists of both the data and the response. The Failure type is set to URLError .

TryMap

Since DataTaskPublisher could result with an error we need to handle it.

tryMap takes a closure as its parameter and returns a new element. If the closure throws an error, the publisher fails with the thrown error. Here we can also check the response and the status code.

Decode

Use decode with a JSONDecoder to decode data received from a DataTaskPublisher using the Decodable protocol.

Map

Combine’s map operator performs a function similar to that of map in the Swift standard library: it uses a closure to transform each element it receives from the upstream publisher. You use it to transform from one kind of element to another.

I think that the functional representation makes it easier to reason about the code.

Next, we will load jokes that match the specified categories, starting with just one joke for now. The code implementation is almost identical, we will refactor it later..

Our next step is to link these methods together. Let’s create a new function where the magic happens and all these methods are chained.

FlatMap

The flatMap(maxPublishers:_:) operator transforms a publisher into completely new publisher that produces elements of the same type. It is used when you want to reach into inner publisher to get its elements.

First we load the categories, when we finish loading the categories, flatMap will pass the result to loadJoke and execute the second request.

The final step is to subscribe to the publisher. We can do it in the updateJokes method that we left empty.

We use our loadData method to get the publisher. Use the receive(on:options:) operator to receive results and completion on a specific scheduler, such as performing UI work on the main run loop. This operator changes the execution context of downstream messages.

We use sink(receiveCompletion:receiveValue:)to observe values received by the publisher and process them using a closure you specify. This method creates the subscriber.

To hold on to the subscription we need to store it in a set of AnyCancellable.

Run the app to see the result. We have chained two requests and retrieved one quote.

Now we can refactor the loading functionality to the final implementation and gather it in the extension:

I’ve made the load function generic so that we can refactor loadCategories and loadJoke. Additionally, I have included a loadJokes method that enables us to download a random number of jokes, as opposed to just one as we previously had.

We could do it with the help of MergeMany publisher which takes an array of upstream publishers and merge them. In order to get an array of jokes we need to apply count operator which emits arrays of at most count elements from an upstream publisher. If the upstream publisher finishes before collecting the specified number of elements, the publisher sends an array of only the items it received. This may be fewer than countelements.

Note that MergeMany finishes responses of requests in a random order. Use Zip to preserve the order.

Final result:

Final Result

Conclusion

This is a simple way to execute one or many requests in a sequence when there is a dependency between them using Combine. With Combine, we get all the abstractions like AnyPublisher for free, making the process much simpler. There’s no need to handle mutable state, semaphores, or dispatch groups doing it the old way with completion closures, and it is easy to inspect, read, and develop the code.

You can find the final project on GitHub.

Thank you for taking the time to read this article. I hope you found it useful! If you have any questions or comments, please don’t hesitate to leave them below. I’m always interested in hearing your thoughts.

In the next article, I will demonstrate how to use UITableViewDiffableDataSource to refactor UITableView.

<hr><p>Handling Multiple Requests Using Combine in Swift was originally published in ITNEXT on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>