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.
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:
- 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.
- 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.
- 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.
- 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.
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.
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.
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.
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.
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.
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.
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.
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.
Run the playground, and you should see values printing to the console. For the full code, please check out the WWT GitHub.
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.