Handling Network Problems Gracefully with Alamofire
Learning to gracefully handle network failures is an essential part of HTTP networking. In this post I will discuss how I use the popular networking library, Alamofire, to handle network failures in my iOS applications.
A Prelude
Mobile devices allow us to remain ever connected to the world around us while on the go, but with this comes the sporadic issues of wireless communication such as signal loss. For this reason, when developing for iOS it is especially important to anticipate that your users are constantly on the move, and that network conditions are constantly changing. It is up to you, the developer, to decide how applications should operate when network connectivity states change. The most common networking issue that I experience on an almost daily basis is connection loss due to moving outside of the range of a connected Wi-Fi network. However my users may live in a major metropolitan area so I may also have to worry about the intermittent signal of cellular data while traveling in a subway. Luckily, the Alamofire RequestRetrier
protocol will alleviate both of these issues.
The Solution
Alamofire created a protocol called the RequestRetrier
that allows for requests to be retried when an error occurs. They also mention how it could be adapted to refresh access tokens, but that will be another post. In order to get the retrier to work, you must first create a class that conforms to the RequestRetrier protocol.
Note:
This is a quick and rough implementation of a
RequestRetrier
that should be further configured to uniquely handle different errors (401, 404, 500, etc.) before it is used in a production application.
The Code
import Alamofire
class NetworkRequestRetrier: RequestRetrier {
// [Request url: Number of times retried]
private var retriedRequests: [String: Int] = [:]
internal func should(_ manager: SessionManager,
retry request: Request,
with error: Error,
completion: @escaping RequestRetryCompletion) {
guard
request.task?.response == nil,
let url = request.request?.url?.absoluteString
else {
removeCachedUrlRequest(url: request.request?.url?.absoluteString)
completion(false, 0.0) // don't retry
return
}
guard let retryCount = retriedRequests[url] else {
retriedRequests[url] = 1
completion(true, 1.0) // retry after 1 second
return
}
if retryCount <= 3 {
retriedRequests[url] = retryCount + 1
completion(true, 1.0) // retry after 1 second
} else {
removeCachedUrlRequest(url: url)
completion(false, 0.0) // don't retry
}
}
private func removeCachedUrlRequest(url: String?) {
guard let url = url else {
return
}
retriedRequests.removeValue(forKey: url)
}
}
Now once you have created your NetworkRequestRetrier, the next thing you need to do is create a session manager so that you can use the request retrier.
import Alamofire
let sessionManager = SessionManager() // Create a session manager
let requestRetrier = NetworkRequestRetrier() // Create a request retrier
sessionManager.retrier = requestRetrier // Set the retrier
// Now when you make a request, use the session manager you just created
sessionManager.request("https://httpbin.org/get").validate().responseJSON { response in
switch response.result {
case .success:
print("Validation Successful")
case .failure(let error):
print(error)
}
}
That’s it!
It doesn’t take much to get the RequestRetrier
working, and that’s a good thing because it is a huge necessity for mobile communication.
Further Reading & Additional Resources
The apple developer documentation has an incredible section on designing for real-world networks that I would highly recommend familiarizing yourself with that applies to more than just AppleOS development.
I also recommend checking out the Alamofire documentation that covers adapting and retrying requests.
subscribe via RSS
Unless otherwise mentioned in the post, all projects are side projects which I work on in my freetime on weekends and evenings, and are not affiliated with my work or employer.
All views and opinions are my own.