Alamofire Tutorial for iOS: Advanced Usage
In this tutorial, you’ll learn about the advanced usage of Alamofire. Topics include handling OAuth, network logging, reachability, caching and more. By Vidhur Voora.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Alamofire Tutorial for iOS: Advanced Usage
25 mins
- Getting Started
- Custom Session and URLSessionConfiguration
- Customizing Session
- Logging Network Requests and Responses Using Event Monitor
- GitHub Authorization
- OAuth Overview
- Creating GitHub OAuth App
- Logging Into GitHub
- Fetching User Repositories
- Request Overview
- RequestInterceptor Overview
- Integrating RequestInterceptor
- Routing Requests and URLRequestConvertible
- Network Reachability
- Caching Using ResponseCacher
- Where to Go From Here?
Integrating RequestInterceptor
In Networking, create a new Swift file named GitRequestInterceptor.swift. Open the file and add:
import Alamofire
class GitRequestInterceptor: RequestInterceptor {
//1
let retryLimit = 5
let retryDelay: TimeInterval = 10
//2
func adapt(
_ urlRequest: URLRequest,
for session: Session,
completion: @escaping (Result<URLRequest, Error>) -> Void
) {
var urlRequest = urlRequest
if let token = TokenManager.shared.fetchAccessToken() {
urlRequest.setValue("token \(token)", forHTTPHeaderField: "Authorization")
}
completion(.success(urlRequest))
}
//3
func retry(
_ request: Request,
for session: Session,
dueTo error: Error,
completion: @escaping (RetryResult) -> Void
) {
let response = request.task?.response as? HTTPURLResponse
//Retry for 5xx status codes
if
let statusCode = response?.statusCode,
(500...599).contains(statusCode),
request.retryCount < retryLimit {
completion(.retryWithDelay(retryDelay))
} else {
return completion(.doNotRetry)
}
}
}
Here's a step-by-step breakdown:
The method inspects and adapts the request. Because the completion handler is asynchronous, this method can fetch a token from the network or disk before making the request.
Here, you fetch the token from the keychain and add it to an Authorization header. The access token for GitHub OAuth Apps doesn't have an expiration time. However, the user who authorized the app can revoke it through GitHub settings.
Here, you check if the response code contains a 5xx error code. The server returns 5xx codes when it fails to fulfill a valid request. For example, you could get a 503 error code when the service is down for maintenance.
If the error contains a 5xx error code the request is retried with the delay specified in retryDelay
, provided the count is within retryLimit
.
- You declare two constants,
retryLimit
andretryDelay
. They help enforce limits on the number of attempts for retrying a request and the duration between retry attempts. -
RequestAdapter
is part ofRequestInterceptor
. It has a single requirement,adapt(_:for:completion:)
.The method inspects and adapts the request. Because the completion handler is asynchronous, this method can fetch a token from the network or disk before making the request.
Here, you fetch the token from the keychain and add it to an Authorization header. The access token for GitHub OAuth Apps doesn't have an expiration time. However, the user who authorized the app can revoke it through GitHub settings.
-
RequestRetrier
has a single requirement,retry(_:for:dueTo:completion:)
. The method is called when a request encounters an error. You have to call the completion block withRetryResult
to indicate whether the request should be retried.Here, you check if the response code contains a 5xx error code. The server returns 5xx codes when it fails to fulfill a valid request. For example, you could get a 503 error code when the service is down for maintenance.
If the error contains a 5xx error code the request is retried with the delay specified in
retryDelay
, provided the count is withinretryLimit
.
Open GitAPIManager.swift. Add the following code below let networkLogger = GitNetworkLogger()
in sessionManager
:
let interceptor = GitRequestInterceptor()
Here, you define interceptor
as an instance of GitRequestInterceptor
. Replace Session
initialization with the following in sessionManager
:
return Session(
configuration: configuration,
interceptor: interceptor,
eventMonitors: [networkLogger])
With this code you pass the newly created interceptor
in the constructor of Session
. All requests belonging to sessionManager
are now intercepted via the instance of GitRequestInterceptor
. Build and run.
Voilà! Now you'll see repositories fetched from your GitHub account.
Routing Requests and URLRequestConvertible
So far, when making a network request, you've provided the URL path, HTTP method and query parameters for each request. As the app size grows, it's essential to use some common patterns for building the network stack. A Router design pattern helps by defining each request's route and components.
In Networking, open GitRouter.swift. You'll see all the requests you've made so far, captured as different cases in the enum. Use this GitRouter
to construct your requests.
Add the following extension to the end of GitRouter.swift:
//1
extension GitRouter: URLRequestConvertible {
func asURLRequest() throws -> URLRequest {
//2
let url = try baseURL.asURL().appendingPathComponent(path)
var request = URLRequest(url: url)
request.method = method
//3
if method == .get {
request = try URLEncodedFormParameterEncoder()
.encode(parameters, into: request)
} else if method == .post {
request = try JSONParameterEncoder().encode(parameters, into: request)
request.setValue("application/json", forHTTPHeaderField: "Accept")
}
return request
}
}
Here's a breakdown:
- You add an extension to
GitRouter
to conform toURLRequestConvertible
. The protocol has a single requirement,asURLRequest()
, which helps construct aURLRequest
. Conforming toURLRequestConvertible
helps abstract and ensure the consistency of requested endpoints. - Here, you construct the request using the properties in
GitRouter
. - Based on the HTTP method, you encode the parameters using either
URLEncodedFormParameterEncoder
orJSONParameterEncoder
. The Accept HTTP header is set for POST request. You return the request after constructing it.
Now, open GitAPIManager.swift. You'll update all the request methods to use GitRouter
.
In fetchCommits(for:completion:)
, delete the line that begins let url =
. Now, replace sessionManager.request(url)
with the following to use your new router:
sessionManager.request(GitRouter.fetchCommits(repository))
In searchRepositories(query:completion:)
, delete everything before sessionManager.request...
. Now, replace sessionManager.request(url, parameters: queryParameters)
with:
sessionManager.request(GitRouter.searchRepositories(query))
Likewise, in fetchAccessToken(accessCode:completion:)
, delete everything before sessionManager.request...
. Now, replace the sessionManager.request(...)
call with:
sessionManager.request(GitRouter.fetchAccessToken(accessCode))
Finally, in fetchUserRepositories(completion:)
, delete everything before sessionManager.request(url, parameters: parameters)
and replace that line with:
sessionManager.request(GitRouter.fetchUserRepositories)
You remove the URL, query parameters and headers declared locally in each of these methods since you no longer need them. GitRouter
constructs URLRequest
s for each request. Build and run.
You'll see your repositories load as before except the underlying requests use GitRouter
.
So far, the app works well. With a good network, the results are almost instantaneous. But the network is one unpredictable beast.
It's important to know when the network isn't reachable and inform the user in your app. Alamofire's NetworkReachabilityManager
at your service!
Network Reachability
In the Networking group, open GitNetworkReachability.swift. Add the following just before the closing brace:
// 1
let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")
// 2
func startNetworkMonitoring() {
reachabilityManager?.startListening { status in
switch status {
case .notReachable:
self.showOfflineAlert()
case .reachable(.cellular):
self.dismissOfflineAlert()
case .reachable(.ethernetOrWiFi):
self.dismissOfflineAlert()
case .unknown:
print("Unknown network state")
}
}
}
GitNetworkReachability
provides a shared instance. It includes the functionality to show and dismiss an alert. Here's what you added:
- Alamofire's
NetworkReachabilityManager
listens for the reachability of hosts and addresses. It works on both cellular and WiFi network interfaces. Here, you create a property,reachabilityManager
, as an instance ofNetworkReachabilityManager
. This checks for reachability using www.google.com as the host. -
startNetworkMonitoring()
listens for changes in the network reachability status. If the network isn't reachable, an alert is displayed. Once there's a network that's reachable through any of the network interfaces, the alert is dismissed.
Now, open AppDelegate.swift. Add the following in application(_:didFinishLaunchingWithOptions:)
right before return true
:
GitNetworkReachability.shared.startNetworkMonitoring()
Here, you call startNetworkMonitoring()
on GitNetworkReachability
to start listening for network reachability status when the app launches.
Build and run. Once the app launches, turn the network off.
The app shows the user an alert when the network isn't reachable and dismisses it when it's reachable. Great job being user-centric!
Sometimes showing an alert isn't the ideal experience. Instead, you might prefer showing previously fetched app data when there's no network. Alamofire's ResponseCacher
is here to help. :]