Async/Await: Generic Network Layer With Swift 5.5

And how you can maximize it

Victor Catão
Better Programming

--

Servers
Photo by Taylor Vick on Unsplash.

Receiving a response from the server is one of the coolest things when we are learning how to develop a frontend application. It’s in this interaction that the app comes to life.

This article will show you how to make a simple and generic network layer for your iOS application without having to write a lot of code every time you make a request, avoiding boilerplate.

If you haven’t mastered the API concept yet, I strongly recommend you read this article before continuing. In addition, it’s also important to have a good understanding of Protocol Oriented Programming (POP) and the async/await structure concurrency.

TMDB: The Movie Database

To help us in this article, we will use TMDB’s API to make some requests, which you can see in the documentation here. Briefly, TMDB is a database that contains information on movies, TV shows, series, etc.

Using the API is free, but that doesn’t mean it’s completely open. In order to make requests, you’ll need an access token, which you can get by registering on TMDB’s page after following the steps below:

1. Go to the TMDB page: https://www.themoviedb.org/

2. Click on “Join TMDB”

3. Register and then login

4. Go to Settings > API: https://www.themoviedb.org/settings/api

Done. At the bottom of the page, you will find your token in the API Read Access Token (v4 auth) section. It’s something like this:

A long string of letters and numbers
TMDB Access Token.

Project

So you can follow the article, I went ahead and created this example project that uses the MVC architecture. You can clone the repository and open the .xcodeproject to follow along. In that file, you will find these three folders where the main files that we’re going to address in this first moment are located.

Project folders.
  • Endpoint: A protocol to set up all endpoints.
  • HTTPClient: One of the most important files for this article. This is where the method that executes the generic requests is located.
  • HTTPMethod: Enum with the main HTTP methods to perform requests. If needed, you can add others like head, trace, options, etc.
  • RequestError: Enum with some errors that we will manage. If the API you consume or your app has other particular cases, add them to this enum.
  • MoviesEndpoint: Enum with TMDB API Movies service endpoints. For each case, it is possible to configure the specific endpoint, determining the path, method, header and body variables.
  • MoviesService: Struct responsible for performing Movies service requests.
  • TopRated: Model to decode the response from the top_rated endpoint.
  • Movie: Model to decode responses that contain movie(s).
  • MainViewController: The only Controller of the project, responsible for making the request and showing the response.
Example project

Network Layer

Before getting to the coolest part of the article (HTTPClient), we need to go through other parts so you can understand everything that is going on.

Endpoint is a protocol in which all endpoints need to conform to inform all their details: scheme, host, path, method, header and body.

The Endpoint protocol has a default implementation of scheme and host as it is normally just one for all endpoints. Therefore, it is not necessary to implement it all the time. If necessary, you can override this implementation.

The path variable will be used as a complement to scheme and host to form the endpoint URL. It works like this: scheme + host + path. For instance: suppose the endpoint URL is https://api.themoviedb.org/3/movie/top_rated, then the path should be /3/movie/top_rated.

The header variable should return a dictionary with all the header information that the endpoint documentation asks for, when needed. This is usually where authentication is performed through Authorization, which you will see below.

Like the header variable, when necessary, the body is also a dictionary that must contain the body information that the endpoint documentation asks to be sent.

The type of the method variable is RequestMethod and represents the HTTP methods of the endpoint, which can be: GET, POST, PUT, DELETE, PATCH, HEAD, etc. For this API, only the top five methods are included, but you can add more if it makes sense in your context.

The RequestError enum contains some possible errors that the HTTPClient will identify and return so that it can be handled by the app.

This enum has the customMessage variable, which can be used to display a feedback message to the user or to track errors, for example.

The TopRated struct is a Codable model to decode the response from the top_rated endpoint of the TMDB API. Note that it has the CodingKeys enum which was used to rename the Snake case entries (sent by the API, as total_pages) to Camel case, which is the most used pattern in Swift.

The Movie struct follows the same idea.

Well, now that you know all the necessary parts to understand the HTTPClient, you finally got to the most important part of this article.

HTTPClient is a protocol with a default implementation of the sendRequest function, responsible for making the request to the server. As a parameter, it receives the Endpoint and the type of the model to decode the API response.

As a return, we’ll have a Result that can be a success with the decoded response or an error with one of the cases created in RequestError.

And as you can see, it is accompanied by the keyword async, which represent that it is an asynchronous function that will run on a separate thread and can return the Result at any time.

As previously stated, the Endpoint protocol contains all the information needed to consume it. This is where all this information will be used.

The method starts by creating the URL we’re going to use with URLComponents. Here’s where we’ll use the endpoint’s scheme, host and path to configure the urlComponents object. Through it we will get the URL to be used in the creation of the URLRequest and, in case of any failure, the .invalidURL error will be returned.

After that, we need to create a URLRequest for the endpoint consumption. But wait, what is this URLRequest? Apple documentation will help us:

URLRequest encapsulates two essential properties of a load request: the URL to load and the policies used to load it. In addition, for HTTP and HTTPS requests, URLRequest includes the HTTP method (GET, POST, and so on) and the HTTP headers.”

So, what the code does next is create an URLRequest from the instantiated URL. After that, the httpMethod and allHTTPHeaderFields are configured through, respectively, the method and header variables of the Endpoint passed by parameter.

The httpBody is also configured following the same logic, in case there is any data to be sent to the endpoint.

Now all you need to do is make the request. We’re close! For that, we will use the URLSession.shared.data with our URLRequest that we just created. But wait a minute, what is URLSession? Again, let’s turn to Apple’s documentation:

“The URLSession class and related classes provide an API for downloading data from and uploading data to endpoints indicated by URLs.”

In other words, it’s the class that abstracts all communication between the app and the API. The URLSession, with all the information necessary to carry out the request, will return the API response.

In the line below, the return tuple of the URL.shared.data() function is created. Note that it’s necessary to use a try because an exception can be thrown when running this function (this is why we are using a do/catch statement).

In addition, it also has the await keyword, which will cause the next line to be executed only when the URL.shared.data() function finishes and returns the tuple.

Now the work is practically finished. If the API returned any response, we proceed with the analysis of the statusCode and, if not, we return the error .noResponse from RequestError.

Since the API returned a response, the analysis of the statusCode is done on top of the possible HTTP statuses. For this article, an ideal case was screened, where [200, 201, …, 299] would be considered a success and therefore we can try to decode the response, transforming the received JSON into a Decodable model and finally returning to the responsible method can use it.

The 401 (unauthorized) case, usually caused by an expired session, in which each context requires an action by the app (e.g., asking the user to log in again), was also considered.

Usage

Now that you understand how the network layer works, it is necessary to understand how to use it. Follow me, it’s easy!

For all endpoints within the Movies service, we will use MoviesEndpoint.

MoviesEndpoint is an enum that conforms to the Endpoint protocol. It has all the information needed for each endpoint. To add a new Movies service endpoint, just add a new case and complete the information inside each variable: path, method, header and body.

Finally, let’s talk about the Service, which is the struct responsible for making the request.

MoviesService is the struct used by the app to make requests. Note some important points here:

  • It conforms to the HTTPClient protocol, which means that it has a sendRequest function internally to perform requests and, therefore, does not need to repeat code for each request.
  • It conforms to the MoviesServiceable protocol, which is critical for testing and dependency injection. You will see more details soon.
  • The function declaration has the keyword async, following the same logic already commented on in the HTTPClient, showing that it is an asynchronous function.

Now that you understand how Service structs work, just see how they are used in MainViewController.

The MoviesService we created will be used through dependency injection of an object that conforms to the MoviesServiceable protocol. The MainViewController was instantiated like this:

There are other ways this dependency injection could have been done, for example, the MVVM architecture could be used and a ViewModel with the service inside it injected into the ViewController. However, for the sake of brevity, we are going to use the method shown above.

Now, all that’s left is to actually make the request using the injected Service. As we are using an async function, we need to create a Task to run the asynchronous code. See Apple’s documentation for this feature:

“Use this function when creating asynchronous work that operates on behalf of the synchronous function that calls it.”

The await will cause the switch line to be executed only when the request returns a Result<Movie, RequestError>. Upon returning, it is possible to make the switch to manage it as needed within each context.

That’s it! Now, let’s see a little bit about how we can test our network layer and use mocks.

Giphy.

Tests

Now you just need to know how to test your network layer and use mocks. The project has the four files below:

  • RequestAppTests: Class responsible for performing app tests.
  • Mockable: A protocol for converting JSON files to Codable.
  • top_rated_response: It’s a .json file with a return example from the topRated TMDB API.
  • movie_response: It’s a .json file with an example of a return from the detail API of a TMDB movie.

Mockable is a protocol that avoids boilerplate with a default implementation of the loadJSON function, which reads an internal .json file and converts it into a given Codable model.

No big deal, right? Now see the code below to understand how we can use the Mockable protocol and MoviesServiceable to create a mock.

Do you remember that our Service class used in the app (MoviesService) also conforms to the MoviesServiceable protocol? Do you also remember that when we used dependency injection on the ViewController we injected an instance of type MoviesServiceable and not a MoviesService? That way you can inject another different instance to be able to reuse and test your ViewController. See the mock case below:

As the loadTableView function is responsible for making the request and updating the movies variable, then the movies.count must be equal to the number of items in my top_rated_response.json, which in this case is just one. In addition, the code also tests if the title of this item is the same title contained in the JSON file.

It is also possible for you to test only your mock, like this:

There are several ways to implement the tests. For instance, we could have a ViewModel with the Service injectable to test requests and various functions that the ViewModel is responsible for and separately perform UI tests with the ViewController.

To use other JSONs in the mock return, we could create other Mockables & MoviesServiceable classes with different returns, or make the MoviesServiceMock class more reusable, injecting filenames, failure types, etc.

It would also be possible to make the network layer more robust with cache implementation, multipart, managing other status codes, etc. Also, we could inject JSONDecoder and URLSession itself to make it even more reusable and testable. The article was based on a simple context, but each context requires specific implementation and complexity.

Thanks for reading, and let’s keep coding!

Want to Connect?You can add me on LinkedIn.

--

--

Sr. iOS Developer at Uber, traveler, passionate about technology 🇧🇷