Consuming Web Services with Swift and ReactiveX

Guille Gonzalez
5 min readJan 25, 2016

--

For a better reading experience, you can also find this post at https://gonzalezreal.github.io/2016/01/25/consuming-web-services-with-swift-and-reactivex.html.

Let’s forget Alamofire or Moya for a moment and build a web API client from scratch. In the process, we will learn how to model web API requests using an Enum, map JSON without any third-party library and use RxSwift to compose our API calls.

It is much more simple than you think!

Borders

As an example, we are going to use the REST Countries API to list the countries that share borders with another given country.

Our sample app showing the borders of Germany

Countries API

In order to find the borders of a given country, we need to issue two requests. First we get the country details using the country name:

As you can see, the borders are returned in an array of country codes. The second API call will get the countries for those country codes:

Modeling the API

Let’s create a protocol that describes a REST API resource in the most generic way:

We could add other stuff like body, etc. but this should be enough for our purposes.

All of our requests will be GET, and a method to create an NSURLRequest based on a Resource would come handy. Let’s create a protocol extension for that:

Nice! NSURLComponents does all the heavy-lifting and converts the parameters dictionary into a URL-escaped query.

With this foundation in place, we can model our Countries API calls using an Enum that implements the Resource protocol:

This is really cool. We have leveraged the enum associated values to abstract away how the parameters are laid out in the request, and hidden all the hard coded strings.

Now creating a request for our API is really simple:

Simple JSON Decoding

JSON decoding in Swift used to be cumbersome, leading to problems like the Optional Pyramid of Doom. There are, literally, hundreds of Swift JSON libraries for that reason.

But with Swift 1.2 things got much simpler. Even though there are still some nice libraries out there, we are going to implement a simple JSON decoding solution.

First of all, let’s create a protocol for our JSON decodable types:

Simple enough. Now we are going to implement some helper functions to decode types conforming to JSONDecodable from an array of JSON objects, a single JSON object, and an NSData object respectively:

Notice the use of flatMap to remove the nil results of mapping the dictionary to the JSONDecodable type.

We are ready to create a Country model that can be decoded from JSON:

The name and nativeName properties are mandatory and the constructor will fail if they are not present in the JSON object.

Now we can easily create a Country from a given JSON object:

The API Client

Let’s start by implementing an error type for our API Client. We need to communicate when the client got a system error or an invalid HTTP status, and also when JSON decoding failed:

Covering all the details of a full-featured networking API like NSURLSession in a single post is not possible. Let’s just concentrate on the most straightforward way to fetch a network resource:

First we create a configuration object and a session based on that object. A configuration object defines the behavior and policies for a session: timeouts, caching policies, additional HTTP headers, etc.

Next we create a data task providing our request and a closure that handles the data after it has been fully received. Note that, unless otherwise specified, the session will call the closure from its own private queue.

Let’s create an APIClient class to wrap this behavior providing a method that returns an Observable instead of taking a closure as a parameter:

Let’s review the creation of Observable<NSData>:

  • Observable.create() takes a closure that will be executed every time an observer subscribes to the Observable. That is, each subscription will trigger a new network request. This concept is called Cold Observable.
  • Inside the network request completion closure, we check for errors or invalid HTTP status codes, and notify the observer accordingly using onError() and onNext() methods.
  • The subscription closure returns an AnonymousDisposable object, which encapsulates the code that will be called when the subscription is torn down.

We are ready to introduce a new method that will map the data returned by the network request into our own model objects:

Here comes the beauty of RxSwift, we can treat Observable as any other container type like Array or Dictionary and map the contents into something else. In this case, we are leveraging our JSON decoding function to map the data into an array of model objects.

Let’s finish our API client by creating an extension that provides the specific methods we need for our app:

As you can see, the infrastructure we have created pays off. It is really simple to add new methods to our API client using our core implementation.

Chaining Requests

Recall that, in order to obtain the list of countries bordering a given country, we need to chain these two requests:

  • First we obtain the country details, including country codes of the countries bordering it.
  • Then we obtain the list of countries for those country codes.

We can use flatMap or flatMapLatest to chain network requests or any other asynchronous operation:

flatMap in action

The flatMap operator will transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable.

The View Model

The view model for our application main screen needs to expose an Observable that sends an array of borders when the network requests complete.

We need to chain countriesWithCodes() after countryWithName() and then map the resulting countries to our Border type. We also have to make sure that results are delivered in the main thread, and that multiple subscriptions won’t trigger additional network requests:

The UI

Thanks to the extensions provided by RxCocoa, we can bind our view model borders property with the table view using a few lines of code:

Wait… Where are the UITableViewDataSource methods? What kind of black magic is this? This ‘magic’ is provided by the RxDataSourceStarterKit, a set of classes that implement fully functional reactive data sources for UITableViews and UICollectionViews, included with RxCocoa.

Binding to a reactive data source requires an observable whose next’s values are arrays and a closure that will receive each item and return the corresponding cell.

By the way, if you are wondering why the dequeueReusableCell method is not taking any identifier as a parameter, check out this post.

What’s Next?

If you’re curious about how to write unit tests for what we just did, take a look at the complete sample code, which you can find here.

The approach is to stub successful and failed requests with the help of OHHTTPStubs to test the core objects method in APIClient.

Once we have that, testing the Country API is just a matter of checking that the Resource protocol methods are returning the appropriate values.

--

--

Guille Gonzalez

Occasional mobile developer. Jira ticket pusher. Converting coffee into code since the nineties. Comic book lover and movie buff. Tech Lead at @TuentiEng