Building a Modern Networking Layer in Swift
Exploring the API
Most applications talk to a remote API in some way, shape, or form. Networking is therefore an important aspect of software development. Sending a request to a remote API isn't difficult, but there is more to it. In this series, you build a modern networking layer for a Swift application using Foundation's
URLSession API. We cover a range of topics, basic and more advanced, from making a simple
GET request to signing requests with an access token. We make use of generics and protocol-oriented programming to create a networking layer that is flexible, testable, and easy to extend.
Building a Basic Client for Cocoacasts
The starter project of this series is a basic client for the Cocoacasts website. The application has two tabs, the What's New tab shows the recently published episodes and the Profile tab offers the user the option to sign in and sign out. Tapping an episode takes the user to the episode view. The user can watch the episode by tapping the Play Episode button.
The application currently doesn't interact with a remote API. The list of episodes is included in the application bundle and the ability to sign in doesn't work. Fixing these issues is the focus of this series.
Adopting the Model-View-ViewModel Pattern
SwiftUI drives the user interface of the application. Unlike a traditional UIKit application, controllers are absent in a SwiftUI application. To keep the views of the application dumb and lightweight, the project adopts the Model-View-ViewModel pattern.
Each view is backed by a view model and it is the view model that is responsible for interacting with the remote API through an API client, not the view. This has a number of advantages that we explore in this series.
Building a Flexible, Extensible API Client
The view models delegate networking operations to an API client. It is the API client that does the heavy lifting. The API client hides any implementation details from the view model, for example, which endpoint it uses to fetch the list of episodes.
We have two goals in mind as we build the API client. First, the API client should be easy to extend. Adding support for a new endpoint shouldn't be complicated. Second, the API client should be easy to mock to facilitate testing. Writing unit tests for view models that depend on the API client should be straightforward.
Exploring the API
Let's take a look at the mock API we will be integrating with. In this series, I will be using Paw to interact with the mock API. You can use any HTTP client, though. On the left, you see an overview of the endpoints we will be integrating into the project.
Fetching the list of episodes is the first task we will tackle in this series. The
/episodes endpoint returns the recently published episodes as a JSON response. The only requirement for this endpoint is the presence of a header with name
X-API-TOKEN. The value is a predefined string that is hard-coded in the project.
Including a header for an API token or an API key is useful for APIs that are used by multiple clients. The API token or key can be used to identify the client and can be revoked in case of misuse or an emergency. It is important to understand that an API token or key offers little to no security, especially if the API token or key doesn't expire or isn't rotated frequently. It isn't difficult for someone with less good intentions to obtain an API token or key.
The client will use the
/auth endpoint to identify the user. The client authenticates the user using basic authentication. If the request is successful, the API returns an access token and a refresh token. The client uses the access token to sign requests for protected resources. The API uses the access token to identify the user. This is much safer than sending the user's credentials with every request.
The client will use the
/auth/refresh endpoint to refresh an expired access token. Access tokens are valid for a limited time. This can be a few minutes, a few hours, or a few days. When the access token is no longer valid, the client uses a refresh token to request a new access token. A refresh token prevents the user from being signed out every time the access token expires. A refresh token is valid for much longer than an access token. Refreshing an access token isn't difficult, but there are a few challenges we need to overcome.
If the client needs to access a protected resource, it needs to sign the request with an access token. This simply means adding an Authorization header that stores the access token. That brings us to the
/videos/:id endpoint. The user needs to be signed in to watch episodes. To watch an episode, the client requests the URL of the video though the
/videos/:id endpoint. It passes the identifier of the video to the API and signs the request with the access token it requested earlier. The response includes a URL that is valid for a limited time.
The next three endpoints offer the ability to fetch, update, and delete the user's progress for a video. Through these endpoints, you learn how to interact with a remote resource, send
DELETE requests, and handle empty responses.
In the next episode, you learn how to fetch the list of episodes from the mock API. Instead of using a third party library, we use Foundation's
URLSession API. I show you how to fetch the list of episodes using a traditional imperative approach and a more modern reactive approach.