Alamofire iOS Advanced Techniques
Alamofire is an essential networking tool for any iOS developer to have under their belt. The framework is elegantly written entirely in Swift.
The framework itself serves as an abstract layer of functionality on top of Apple’s existing networking stack. So, your existing knowledge of the iOS networking landscape is still valid. And, you’ll build on top of it.
In this Alamofire tutorial for iOS, you’ll explore the following topics:
- Building a scalable pattern for organizing your networking stack.
- Handling different error responses.
- Dealing with authentication APIs.
- Setting a retry policy for request failures.
- Dealing with different request types.
- Tracking internet reachability status.
Getting Started
Start by downloading the project materials from this repository.
Open DnDRealm.xcworkspace in the starter project. Build and run.
The app you’ll build lets you search for Dungeons and Dragons equipment and monsters using DnD API. Also, the app will feature fetching monster images using Imgur API. And, you’ll implement images upload.
Ready to explore the Dungeons and Dragons realm? Time to dive in!
Understanding Routing Requests
As an app’s networking endpoints grow in size, the number of lines of code grows along with it. The router pattern is an important part of organizing your networking stack.
A router is a type that defines routes or URLRequest
components. URLRequest
represents information about its Request
, such as URL, parameters, headers, method types and policies. As shown below:
Alamofire provides URLConvertible
and URLRequestConvertible
protocols to simplify the magic of routing.
Creating the DnD Router
Currently, the HTTP requests for fetching and searching use string endpoints. That’s messy. Add a router to encapsulate DnD APIs routes.
In Routers group, create a new file named DndRouter.swift. Then, add the following to the file:
The enum defines two cases, each for a specific route:
1. monsters
fetches the monsters list.
2. equipment(name:)
searches for equipment by name.
Specify the URL paths for each case by adding the following to DnDRouter
:
Here’s the code breakdown:
1.baseURL
defines the DnD API’s starting endpoint.
2. path
specifies the path components for each route.
The code above gives the router its endpoint information, but it’s missing an HTTP method for each case.
Next, add import Alamofire
to the top of DndRouter.swift. Then, add the following to DnDRouter
:
Since both routes are requesting data, you can use a fall-through switch statement. Both cases will return an Alamofire HTTPMethod.get
as their request type. Now, add the route parameters to DnDRouter
:
The monsters route doesn’t have any parameters, indicating the API doesn’t require any. The equipment, however, needs a dictionary with a key-value pair as specified in DnD API docs.
Now the router has all its components, you can define its routes’ URLRequest
.
Add the following extension at the bottom of DndRouter.swift:
The code above constructs URLRequest
as follows:
- It conforms the router to
URLRequestConvertible
and implements its required methodasURLRequest()
. - Then, it constructs the route
URL
by appendingbaseURL
with the routepath
component. - It defines a
URLRequest
instance using the routeURL
. - Then it assigns the
URLRequest.method
property with the routemethod
. - Next, it encodes and embeds the route
parameters
into theURLRequest
instance.URLEncodedFormParameterEncoder
encodes the passed parameters into a URL-encoded string. - Finally, it returns the encoded
URLRequest
.
Now, the DndRouter
routes are ready to be attached to a Request
.
Open MonstersTableViewController.swift. Replace the following code in fetchMonsters()
:
With:
Then, in searchEquipment(with:)
, replace:
With:
DnDRouter
achieves a separation of concerns in your networking stack.
Here’s a diagram illustrating the DnDRouter
pattern.
It shows how DnDRoute
constructs monsters and equipment routes to be attached to a Request
for heading to the network.
Parameter Encoding
Alamofire supports any Encodable
as request parameters.
It provides two ParameterEncoder
conforming types:
- URLEncodedFormParameterEncoder: Encodes values into a url-encoded string. Its
destination
decides where to set this encoding result:
*queryString
: The query of the request’sURL
.
*httpBody
: The HTTP body ofURLRequest
.
*methodDependent
: It usesqueryString
forGET
,HEAD
andDELETE
, buthttpBody
for others. - JSONParameterEncoder: Encodes
Encodable
values using Swift’sJSONEncoder
. It sets the result in the HTTP Body ofURLRequest
.
Both types set the URLRequest
’s Content-Type
header parameter if it’s not already set.
Creating the Images Router
Encapsulate the Images API‘s routes in a separate router.
Now, create a new router to hold the images searching route.
In Routers group, create a new Swift file named ImagesRouter.swift. Then, add the following to the file:
ImagesRouter
defines images(query:)
route for images search using query value.
Next, define the path URL. Add the following in ImagesRouter
:
It’s a GET
method. Add the following:
Then, define the API parameters. Add the following:
As you did for DNDRouter
, add the following extension at the bottom:
Finally, attach images(query:)
route to a request.
Open ImagesCollectionViewController.swift, replace fetchImages()
:
With:
Note: The Dragons keyword increases the probability of getting the search result.
Build and run. You’ll see no differences. :]
Serializing Responses
Before you fix the response error in the images screen, you need to parse and convert it into a suitable format. This’s the response serializer mission.
Fortunately, Alamofire provides three ResponseSerializer
conforming types:
- DataResponseSerializer: Useful for customized data handling. It’s used in
responseData(queue:completionHandler)
. - StringResponseSerializer: Uses
String.Encoding
to parse data toString
.responseString(queue:encoding:completionHandler:)
uses it. - DecodableResponseSerializer: Parses data into specific
Decodable
types. It’s the serializer forresponseDecodable(of:queue:decoder:completionHandler:)
.
Here’s a diagram to show the various components working together:
After routing a request to a server, response handlers receive and serialize the response data.
Creating an Error Response Serializer
The Imgur API has two possible responses: Success or Error, each with a different model.
By checking fetchImages()
from ImagesCollectionViewController
, you’ll find:
1. Success response: responseDecodable(of: Images.self)
serializes it. Additionally, it settles for case let .failure(error)
to show an alert in case of any failure.
2. Error response: There is no handler to consider its serializing.
Here’s the error response:
Note: The starter project provides:
- ImagesErrorResponse: A decodable
struct
for mapping the error response above. - ResponseError: An
enum
that conforms to Swift’sError
protocol to represent your custom error.
This is a good chance to create your ResponseSerializer
for error handling. Besides the API error response, you’ll also handle the connectivity errors.
In Serializers group, create a new Swift file named ErrorResponseSerializer.swift. Then add the following at the top of this file:
The code above creates ErrorResponseSerializer
that conforms to ResponseSerializer
and implements its serialize(request:response:data:error:)
.Then it returns a ResponseError
as its generic type.
For handling the connectivity errors, add the following at the start of serialize(request:response:data:error:)
:
This guard statement guards against the chance of no return data due to internet connectivity or timeout errors.
Alamofire serializers already do much of the heavy lifting. This is a good time to ask them for help in serializing the API error response.
Add the following at the end of serialize(request:response:data:error:)
:
The code above:
- Uses Alamofire’s
DecodableResponseSerializer
to parse the response usingImagesErrorResponse
. Keep in mind, passnil
forerror
inserialize(request:response:data:error:
otherwise, it’ll throw an error without trying to decode the response. - Checks and returns the appropriate
ResponseError
, either authentication or server error.
Note: The starter project defines an Int
extension with isAuthenticationErrorCode()
for matching authentication response codes 401 and 403.
Now, ErrorResponseSerializer
is ready for attaching to the Request
cycle and serializing such responses.
Open ImagesCollectionViewController.swift and add the following at the end of the Alamofire request in fetchImages()
:
This code adds an additional response handler to the Request
instance. It’ll handle the error response if it occurs.
Note: showError(_:)
is defined at the bottom of ImagesCollectionViewController
. It shows a suitable AlertViewController
corresponding to the ResponseError
type.
Before you run, delete the blind failure handling in .responseDecodable(of: Images.self)
. Replace:
With:
Build and run to see the error handling in action.
Then, turn off your internet connection and choose any monster to test how connectivity errors are handled.
Understanding API Authentication
Keeping their data secure is a chief concern of data providers. The three major methods of adding security to an API are HTTP Basic Auth, API Keys and OAuth.
Imgur uses OAuth 2.0. It’s fundamentally the more secure option for authentication and authorization.
The figure above illustrates the main stages of OAuth 2.0. To dive deeper into these stages, the diagram below gives you a focused idea of what’s in store when authenticating with the Imgur API:
The client makes an authorization request for an access token and a refresh token. Using the access token, the client is either able to retrieve resources from the resource server with a valid token. In the case of an invalid access token, the resource server returns an error. In that case, the client would use the refresh token to retrieve new access and refresh tokens.
Note: For more information about OAuth 2.0 check out OAuth 2.0 with Swift.
Registering the App With Imgur
Every OAuth 2.0 service requires you to register your app at some point. Open Imgur Registration to register an account. Then, click here to register an app on their developer portal.
Use the following information to register your application:
- Application name: DnDRealm.
- Authorization type: OAuth 2 authorization with a callback URL.
- Authorization callback URL: rwdnd:\.
Then, enter an email.
Once completed, you’ll see a screen like this:
Copy your Client ID and Client secret into Authentication/AuthenticationKeys.swift as follows:
Cool. :] You’re ready to begin the authorization cycle.
Authorizing the User
The authorization URL
lets the user to login and give permissions to the registered app. Define it as per Imgur API documentation.
Now, open ImagesRouter.swift and add the following below baseURL
:
Next, open ImagesCollectionViewController.swift. InpresentAuthenticationAlert(with:)
, add the following code underneath // TODO: Login Action
:
That’ll open the browser with authorizationURL
when the user taps the alert’s login button.
Build and run. Tap on a table view cell. An alert pops up. Tap the login button. The following will appear:
The app redirects you to the Imgur authentication page. Log in with your account.
Once you’ve successfully logged in, an alert will ask you if you want to open the app. Tap Open. :]
You’ve achieved step one of the Imgur API authentication flow! The starter project has handled the returned tokens and completed step two for you. :]
Note: The starter project provides:
- TokenHandler protocol: It extracts the access and refresh tokens from the callback URL. After that, it stores them in Keychain.
- Authenticator protocol: It’s responsible for retrieving and refreshing the access token.
Note: For more information on dealing with Keychain check out the Keychain Services API Tutorial for Passwords in Swift.
Working With a Request Interceptor
Alamofire’s RequestInterceptor
protocol is composed of RequestAdapter
and RequestRetrier
protocols. They can play a big part in your app authorization cycle.
RequestAdapter
inspects and mutates each URLRequest
before sending it over the network. Therefore, it’s a suitable place to inject the authorization header into URLRequest
. Here’s a diagram illustrating how it interacts in the Request
cycle.
Per adaptation Success, RequestAdapter
appends an extra URLRequest
and URLSessionTask
to the request’s requests
and tasks
properties
Adding the Authorization Header
The authorization request header contains the credentials to authenticate a user with a server. So here you’ll add the access token while adapting the request.
Under Interceptors group, create a new Swift file named ImagesRequestInterceptor.swift.
Add the following:
Here you:
- Create your
RequestInterceptor
class, conforming to: --*RequestInterceptor
to implementRequestAdapter.adapt(_:for:completion:)
.RequestAdapter
executes this method before sendingURLRequest
to the server.
*Authenticator
. Theprotocol
that provides the authentication access token. - Then define a new
URLRequest
instance to be the manipulated. - Check if there is a stored
accessToken
inside the app if the user successfully logged in before. - Next, add the
accessToken
in theURLRequest
’s authorization header usingHTTPHeader.authorization(bearerToken:)
. - Then send the modified
URLRequest
over the network. - If there is no
accessToken
, abort theRequest
with anauthentication
error.
Note: Alamofire’s HTTPHeader
provides authorization types that match to the following authentication approaches:
- HTTP Basic Auth:
.authorization(username:password:)
- API Keys:
.authorization(_:)
- OAuth Token:
.authorization(bearerToken:)
Guess which component handles the authentication
error that appeared in the sixth step of the code above? That’s right, it’s your ErrorResponseSerializer
handler!
Handling Adaptation Error
The adaptation error is that error occurs during the execution of adapt(_:for:completion:)
. Now, you’ll handle this error.
Open ErrorResponseSerializer.swift, add the following code to the top of serialize(request:response:data:error:)
:
The code above checks and returns the adaptation error.
Finally, inject ImagesRequestInterceptor
into the Request
execution cycle.
Open ImagesCollectionViewController.swift, replace the following inside fetchImages()
:
With:
Build and run. You’ll to steps three and four in the authorization cycle complete.
Requesting Retrier Role
RequestRetrier
protocol can retry a request that encountered an error. Therefore, it’s a suitable place to refresh the access token when being invalidated.
Below is a diagram showing the interaction flow of RequestRetrier
:
Retrying a request appends a new URLRequest
to its requests
property.
RequestRetrier
fires within a possible set of errors:
URLRequestConvertible
returns an error when called.RequestAdapter
fails during adaptation.URLSessionTask
completes with errors for various reasons such as networking availability or cancelation.- Response handlers produce error due to invalid response or parsing.
Note: RequestRetrier
can also produce errors, which do not trigger the retry action.
Refreshing the Access Token
When the access token expires or becomes invalid, it’s time for the refresh token to get a new one. Refresh tokens carry the information necessary to obtain a new access token without prompting the user.
Now, you’ll implement your RequestRetrier
for refreshing the access token.
Open ImagesRequestInterceptor.swift, add the following to ImagesRequestInterceptor
:
Breaking this down, you get the following:
RequestRetrier
executesretry(_:for:dueTo:completion:)
when errors occur.- The
guard
preventsRequest
from retrying if it doesn’t bypass the following conditions:
*lastProceededResponse != request.response
: Prevents the body from multiple executions for the same response instance. NoticeRequestRetrier
may execute many times for the same response. For example, an authentication response error and serialization error occur for the same response. As a result, Alamofire fires theirRequestRetrier
twice, one per error.
*request.retryCount < retryLimit
: Limits the maximum number ofRequest
retries.
*let statusCode = request.response?.statusCode
andstatusCode.isAuthenticationErrorCode()
: Ensure it’s an authentication error. - It keeps the reference of the last processed response, as the
guard
above checks for it. - It’s an authentication error. Therefore, it calls
Authenticator.refreshToken(completion:)
, which achieves the following:
* Sends aRequest
for refreshing the invalid access token.
* Stores the new access token to be ready for use. - As a result of refreshing the access token process, it decides to either
RetryResult.retry
orRetryResult.doNotRetry
.
Here’s a diagram illustrating the possible values of RetryResult
.
Note: Alamofire includes a built-in RetryPolicy
interceptor. It enables easy retry when requests fail due to a variety of common network errors.
Finally, to ensure that all are well-suited, move refreshToken request’s information from Authenticator.refreshToken(completion:)
to ImagesRouter
.
Open ImagesRouter.swift, add the following case to ImagesRouter
:
Then, define its path
. Add the following code to path
:
It posts data, so add the following to method
:
Next, construct its parameters
as follows:
Finally, open Authenticator.swift and replace the following code:
With:
Good job! You’ve completed the authorization cycle.
Build and run.
Understanding Request Types
Alamofire’s Request
has different types to fit different purposes. Each type is encapsulated by a particular class with unique properties and functionalities as shown below:
Alamofire’s Request
has three fetching data subclasses, such that DataRequest
accumulates response data into memory, DataStreamRequest
streams the data, but doesn’t accumulate it, and DownloadRequest
downloads the data to disk. In addition, UploadRequest
is a subclass of DataRequest
to upload data, files, or InputStream
to a server.
These subclasses act as a wrapper for URLSessionTask
. Each Request
keeps a copy of URLRequests
and URLSessionTasks
that includes both the initial created from the parameters, as well as the created by RequestInterceptor
.
Note: See Apple Documentation for more information on
URLSessionTask
and its types.
Streaming Images Using DataStreamRequest
Alamofire’s DataStreamRequest
is suitable for long-lasting server connections that receive data over time. It never accumulates data in memory or saves it to disk. You’re about to use it for streaming the images.
Now, check ImagesCollectionViewController.fetchImages()
, you’ll find it saves the response in items: [GalleryItem]
.
This GalleryItem
represents an image, so converting it to a self loadable resource enables Alamofire to attach it to a Request
directly as URL
.
Next, open GalleryItem.swift and add the following extension after // MARK:- Alamofire URLConvertible
:
The code above defines the GalleryItem
URL by conforming to URLConvertible
as discussed in the Understanding Routing Requests section. Simply put, this returns a GalleryItem.url
for URLConvertible.asURL()
.
Now, items
is ready for streaming.
Open ImagesCollectionViewController.swift, add the following to streamImages()
:
The code above:
- Initializes a
DataStreamRequest
for eachGalleryItem
initems
. - Then it validates the response so:
*HTTPURLResponse.statusCode
is within the200..<300
range.
* TheContent-Type
header matches the request’sAccept
value. - Next, it adds a
responseStream(on:stream:)
handler, which is repeatedly called as data arrives. - Then, for the valid data responses, it invokes
GalleryItem.appendData(_:)
, which achieves the following:
* It appends the data inside eachGalleryItem
instance.
* Next, it firesGalleryItem.responseStream
closure. This allows any instance of listening for this stream of data.
CheckImageCollectionViewCell.monitorDataStream(for:)
that listens and displays this stream.
Build and run. The images are downloading. :]
Because the images are fetched randomly, the same image may be fetched many times. What if the same image is fetched again? Will it stream again?!
Yes, it will. However, Alamofire’s CachedResponseHandler
can give you a hand.
Caching Images
Response caching has a big impact on Apps performance. It efficiently stores and reuses previously retrieved responses to serve their future requests.
Alamofire provides ResponseCacher
which conforms to CachedResponseHandler
. It makes it easy to control the caching behavior.
To overcome the image multiple-time streaming, cache its stream response.
Now, open ImagesCollectionViewController.swift. Inside streamImages()
, add the follwing right below .validate()
:
This single line code caches the images by adding Request.cacheResponse(using:)
with Behavior.cache
.
Here are the possible Behavior
values:
cache
stores the response.modify(_:)
modifies the response before storing it.doNotCache
prevents the response from being stored.
Note: For information about the caching rules, check Apple Documentation.
Build and run for one-time streaming.
Uploading Images Using UploadRequest
Are you interested in adding images to your Imgur profile? You’ve already appended each GalleryItem
’s streamed data, which means you’re ready to upload them.
Now, you’ll define the upload route.
Open ImagesRouter.swift. Add the following case to ImagesRouter
:
Then, add the following to path
:
Add the following to method
:
Finally, add the following to parameters
:
Now, define a method to upload a GalleryItem
using this route.
Open ImagesCollectionViewController.swift. Add the following extension after // MARK: — Alamofire UploadRequest
:
The code above defines upload(_:at:)
to upload a GalleryItem
.
Here’s a breakdown:
- Instantiates an
UploadRequest
instance to upload theGalleryItem.data
to theImagesRouter.upload
route. - Listens for errors using
ErrorResponseSerializer
. - In case of error and before showing it, confirm that
GalleryItem
is no longer uploading by settinguploadRequest
tonil
. - Reloads the corresponding cell to reflect the request’s state changes.
- Sets
GalleryItem.uploadRequest
withuploadRequest
. This indicates the runningUploadRequest
for thisGalleryItem
.
Next, you’ll upload a specific GalleryItem
when the user taps an ImageCollectionViewCell
.
So, add the following to ImagesCollectionViewController
inside collectionView(_:didSelectItemAt:)
:
The code above calls upload(_:at:)
for the selected GalleryItem
and IndexPath
if there’s no UploadRequest
already running for this GalleryItem
.
Before you run you need to display both the state and progress properties of this UploadRequest
.
Reflecting the Upload Request State
Get to know the different Request
states before you dive deeper into the code:
A Request
starts in the Initialized state. It can be Suspended, Resumed, and Cancelled by calling the appropriate method. For example, resume()
resumes or starts a Request
, and suspend()
suspends or pauses a request.
The resumed Request
reaches the Finished state once all response validators and serializers have been run. However, if additional response serializers are added to this Request
after that. It’ll transition back to the Resumed state and perform the network request again.
Note: If startRequestsImmediately
is true, Alamofire calls resume()
once a response handler is added to the Request
.
Now, you’re ready to map these different states.
Open ImageCollectionViewCell.swift, add the following code inside the extension below // MARK: — UploadRequest
:
This method reflects the UploadRequest
states to the cell state
.
Then, monitor the upload request progress. Add the following method below updateCellState(with:)
:
The code above:
- Sets the current progress of
uploadRequest
to the cellprogressBar
. - Adds
uploadProgress(queue:closure:)
handler touploadRequest
for progress monitoring. It callsclosure
periodically with theprogress
of the sent data to the server.
Finally, combine these two methods to configure the cell.
Add the following to configureWithUploadRequest(_:)
:
All is ready. Build and run. Tap on an image.
Ooh! You didn’t add ImagesRequestInterceptor
to the UploadRequest
.
Before you add it, Session
has another suggestion!
Creating Images Session
Alamofire’s Session
provides APIs for the various Request
subclasses. Furthermore, it encapsulates a variety of configurations applied to its requests.
Session
provides a default
singleton instance which is used by the AF
namespace. AF.request(“https://...")
is equivalent to Session.default.request(“https://...")
.
All Imgur requests require you to inject an ImagesRequestInterceptor
into their cycle.
Under Sessions group, create a new Swift file named ImagesSession.swift.
Add the following to the file:
This code creates a default
singleton instance for ImagesSession
. It customizes the Session
by assigning an ImagesRequestInterceptor
instance to its interceptor.
Note: For more
Session
configurations, check the Alamofire Documentation.
Now, head back to ImagesCollectionViewController.swift and apply the following changes:
- In
fetchImages()
, replaceAF.request
withImagesSession.default.request
. - In
upload(_:at:)
, replaceAF.upload
withImagesSession.default.upload
.
Build and run. Upload an image. Check your Imgur profile.
Pausing & Resuming the Upload Request
Controlling the uploading state is a nice gift to the user. :]
While you’re still in ImagesCollectionViewController.swift, add the following code below // MARK: — Alamofire Request
:
It adds suspendOrResume()
to toggle the state of Request
between suspended
and resumed
.
To pause and resume the uploading when the user taps an ImageCollectionViewCell
, add the following code below// TODO: Suspend or resume request
:
This reverses the UploadRequest
state for the already uploading cell.
Build and run.
Listening for Internet Reachability
Listening to the network reachability status is useful. However, don’t use it to determine if you should send a network request.
Alamofire provides NetworkReachabilityManager
for such scenarios. It listens for changes in the reachability of hosts and addresses.
Now, you’ll discover how to use it and display the reachability status.
Open MonstersTableViewController.swift. Add the following property to MonstersTableViewController
:
Then, add the following extension below // MARK: — Network Reachability
:
Here, the code:
1. Initializes MonstersTableViewController.reachabilityManager
with Apple host. You can pass any trusted live host to listen.
2. Then, it calls startListening(onQueue:onUpdatePerforming:)
to start tracking the changes of network reachability status.
3. Next, it reflects the reachability status to the UINavigationBar
prompt.
4. The first fetchMonsters()
call in viewDidLoad()
may fail for any reason. Here’s a suitable place to fetch the data once the status becomes reachable
.
Finally, add listenToReachability()
to the bottom of viewDidLoad()
to start listening.
Note: It’s better to use an iOS device rather than the simulator. When running in the simulator and toggling WiFi off, then back on, that the reachability APIs do not report that connectivity was restored due to the runtime behavior of the simulator.
Build and run. Turn off your internet connection to see the status changes.
Conclusion
You can download the final project from the repository below. In this tutorial, you’ve learned Alamofire advanced techniques to deal with different aspects as routing, and authentication cycle. You also learned about using different Request types.
To learn more about behind the scene details, check out URLSession Getting Started tutorial. It’s a great jumping-off point for URLSession requests. If you have any questions or comments on this tutorial, please join the discussion below!
Acknowledge
Special thanks for raywenderlich.com for supervising and guidance.