You can find all the code from this article on the WWT GitHub.

When fetching data from an API, the situation often arises where multiple network calls are needed to obtain the necessary data. While there is a case to be made about using micro-services to abstract this need from the client, that adds more complexity. On the other hand, it can be quite difficult to work with nested closures on the client side when using URLSession.

Thankfully, Apple had this in mind when designing Combine. Combine provides an easy method to make synchronous calls. This allows developers to replace massive amounts of code with more expressive and concise syntax.

MetaWeather API description

Consider the MetaWeather API. There is a Location endpoint that returns a JSON structure with the weather data included. In order to call this endpoint, the WOEID (Where On Earth IDentifier) is required. The Location Search endpoint takes in a query or latitude and longitude to return a JSON structure including the WOEID.

Therefore, the first step is to call the Location Search endpoint for the WOEID then use that to call the Location endpoint. As you can see, these calls will need to be made synchronously.

Top level network layer

First, we will set up the top level structure that actually makes our API calls. Create an empty playground and import Combine and Foundation. This struct provides an easy interface with URLSession and reduces the potential for code duplication across many files.

ApiClient struct with generic make function for network requests

You may be able to determine what this code is doing without knowing any Combine, and that is a testament to the API design. It does the following:

  1. Call dataTaskPublisher on the shared URLSession, which is what publishes the result of a combine call to URLSession's data task. If you are familiar with using URLSession's dataTask function, this is the Combine version.
  2. The dataTaskPublisher function emits values containing data and a response. We only care about the data for this example, so we will map that key path specifically.
  3. Specify that the publisher receives updates on the main queue. As any iOS developer knows that any updates to the UI layer must be on the main queue. This example is UI agnostic, but you can easily add UI to connect to the Combine code.
  4. Erase the specific publisher to any publisher to perform type erasure and increase flexibility. If this was not done, the type would be very complex and require more specific code to extract values.

Model data

The network layer will make use of 3 types to decode the JSON structures provided by the API. We will have Location, WeatherResponse and Weather types.

Note: There are many more values returned by each endpoint, and I omitted most of them for brevity. The structs and properties are public as they are saved in separate Swift files in the playground.

The Location is returned as an array from the Location Search endpoint. We only need the woeid, and the title helps to differentiate locations.

Location struct with title and woeid properties
Location struct with title and woeid properties conforming to Decodable protocol

The WeatherResponse is returned in an array from the Location endpoint, and it also has the title and woeid along with an array of Weather data. We use CodingKeys to remove snake case naming conventions from the API.

WeatherResponse struct with weather, title and woeid properties
WeatherResponse struct with weather, title and woeid properties. It implements custom decoding keys to conform to Decodable.

The Weather structure helps us group the specific weather data. Currently, we are only interested in the date as well as the minimum, maximum and current temperatures for the day.

Weather struct with date, minTemp, maxTemp and currentTemp properties
Weather struct with date, minTemp, maxTemp and currentTemp properties. It implements custom decoding keys to conform to Decodable.

MetaWeather API specific network layer

From here, we need another layer that is specific to the weather API. We will call this WeatherProvider, and it needs an ApiClient and a URL to direct network calls to.

WeatherProvider struct with woeid and weather functions
WeatherProvider struct with woeid and weather functions

We created two functions per the MetaWeather API Description section. The woeid function corresponds to the Location Search endpoint, and the weather function corresponds to the Location endpoint.

The woeid function takes in latitude and longitude and returns a publisher, which has an array of Locations or an Error. The latitude and longitude are used to add a query parameter for the URL. Finally, call the apiClient's make function with the URLRequest.

The weather function takes in a woeid, appends it to the URL and calls make on the apiClient.

Making calls with Combine

Now it's time to make the calls with our structures in place. Create a WeatherProvider, and call the woeid function. I am hardcoding the St. Louis Arch's location below, and ideally this would come from user input or the user's current location.

Creating a weatherProvider and woeidSubscriber
Creating a weatherProvider and woeidSubscriber

We are assuming the first value is correct, and we use compactMap to remove any nil values. Make sure to store this in a variable as we will be adding to this chain. Therefore, we are obtaining the first non-nil location.

Obtaining the first non-nil location
Obtaining the first non-nil location

With that location, we can call the weather function on the weather provider. We flatMap over the firstLocation variable to flatten the data and get access to the location inside directly, and now we can call the weather function with the location's woeid. Store this call in a variable.

Calling the weather function on weatherProvider
Calling the weather function on weatherProvider with the firstLocation's woeid

Finally, we are ready to start receiving values. Use the sink method to subscribe to the publisher. Sink takes two arguments, the first is a closure that runs when the publisher finishes, and the second is a closure that runs each time a value is received. We do not have any code to execute when the publisher completes and can provide an empty closure. For receiveValue, we can simply print the data to the console to ensure receipt.

Subscribe to receive values using sink
Subscribe to receive values using sink on the weatherResponse

Run the playground, and you should see values printing to the console. For the full code, please check out the WWT GitHub.

Wrapping up

After seeing how easy it is to make synchronous calls with Combine, you may never want to go back to using URLSession without it. We created a generic network layer, specific MetaWeather API implementation and made actual calls to the API in under 60 lines of code. 

Combine can help simplify networking layers dramatically, and it can make the code much easier to read and maintain.

Questions? Leave your comment below or feel free to reach out to us directly.

Technologies