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?
GitHub Authorization
To fetch your private repositories, you need to log in to GitHub through your app. There are two ways an app can get authorization to access GitHub API:
- Basic Authentication: This involves passing the username and password as part of the request.
- OAuth 2.0 token: OAuth 2.0 is an authorization framework that gives an app access to user accounts for an HTTP service.
In this tutorial, you’ll learn to work with an OAuth 2.0 token.
OAuth Overview
There are several steps to authorize an app to access user repositories via OAuth 2.0:
- The app makes a network request for authorization.
- Then, the user logs in to GitHub for the authorization to succeed.
- Next, GitHub redirects back to the app with a temporary code.
- The app requests an access token using that temporary code.
- On receiving the access token, the app makes an API request to fetch the user’s private repositories. The request’s authorization header will contain the access token.
Next, you’ll create a GitHub OAuth app.
Creating GitHub OAuth App
Log in to GitHub and follow these steps to create an OAuth app with the settings shown below:
- Enter GitOnFire as the Application name.
- Enter https://www.raywenderlich.com/ as Homepage URL.
- Skip the Application description.
- Enter gitonfire:// as the Authorization callback URL.
Logging Into GitHub
Once you’ve registered an app, copy the Client ID and Client Secret values. Then in your Xcode project, open GitHubConstants.swift and update clientID
and clientSecret
with the corresponding values.
Next, open GitAPIManager.swift and add the following method just before the closing brace:
func fetchAccessToken(
accessCode: String,
completion: @escaping (Bool) -> Void
) {
// 1
let headers: HTTPHeaders = [
"Accept": "application/json"
]
// 2
let parameters = [
"client_id": GitHubConstants.clientID,
"client_secret": GitHubConstants.clientSecret,
"code": accessCode
]
// 3
sessionManager.request(
"https://github.com/login/oauth/access_token",
method: .post,
parameters: parameters,
headers: headers)
.responseDecodable(of: GitHubAccessToken.self) { response in
guard let cred = response.value else {
return completion(false)
}
TokenManager.shared.saveAccessToken(gitToken: cred)
completion(true)
}
}
Here’s a step-by-step breakdown:
To learn more about using keychain and storing secure information, read this KeyChain Services API Tutorial for Passwords in Swift.
- You define the headers for the request.
Accept
withapplication/json
tells the server the app wants the response in JSON format. - Then you define the query parameters
client_id
,client_secret
andcode
. These parameters are sent as part of the request. - You make a network request to fetch the access token. The response is decoded to
GitHubAccessToken
. TheTokenManager
utility class helps store the token in the keychain.
Open LoginViewController.swift. In getGitHubIdentity()
, replace //TODO: Call to fetch access token will be added here
with the following:
GitAPIManager.shared.fetchAccessToken(accessCode: value) { [self] isSuccess in
if !isSuccess {
print("Error fetching access token")
}
navigationController?.popViewController(animated: true)
}
Here, you make a call to fetch the access token using the temporary code. Once the response succeeds, the controller shows the list of repositories.
Now open RepositoriesViewController.swift. In viewDidLoad()
, remove the following line :
loginButton.isHidden = true
This displays the login button. Build and run.
Tap Login to log in. The browser will then redirect you back to the app, and the login button will change to logout. You’ll see the access token and scope in the console.
Great job! Now it’s time to fetch your repositories.
Fetching User Repositories
Open GitAPIManager.swift. In GitAPIManager
, add the following method:
func fetchUserRepositories(completion: @escaping ([Repository]) -> Void) {
//1
let url = "https://api.github.com/user/repos"
//2
let parameters = ["per_page": 100]
//3
sessionManager.request(url, parameters: parameters)
.responseDecodable(of: [Repository].self) { response in
guard let items = response.value else {
return completion([])
}
completion(items)
}
}
Here’s what you added:
- You define the URL to fetch your repositories.
- The
per_page
query parameter determines the maximum number of repositories returned per response. The maximum results you can get per page is 100. - Next, you make a request to fetch your repositories. You then decode the response into an array of
Repository
and pass it in the completion block.
Next, open RepositoriesViewController.swift and find fetchAndDisplayUserRepositories()
. Replace //TODO: Add more here..
with the following:
//1
loadingIndicator.startAnimating()
//2
GitAPIManager.shared.fetchUserRepositories { [self] repositories in
//3
self.repositories = repositories
loadingIndicator.stopAnimating()
tableView.reloadData()
}
Here’s a code breakdown:
By default, Alamofire calls the response handlers on the main queue. So, you don’t have to add code to switch to the main thread to update UI.
- You display a loading indicator before making a network request.
- Then, you make a network request to fetch your repositories.
- Once your repositories are fetched, you set
repositories
with the response and dismiss the loading indicator. You then reload the table view to show the repositories.
Build and run.
The list is empty! Check the Xcode console, and you’ll see a 401 unauthorized request.
You have to pass in the access token in a header for authorization. You could add anAuthentication
header inside fetchUserRepositories(completion:)
in GitAPIManager
. However, the process of adding headers individually for each request may become repetitive.
To help avoid this, Alamofire provides RequestInterceptor
, a protocol that enables powerful per-session and per-request capabilities.
Request Overview
Before diving into RequestInterceptor
, you should understand the different types of Request
s.
Alamofire’s Request
is a superclass of all requests. There are several types:
-
DataRequest: Encapsulates
URLSessionDataTask
by downloading the server response into data stored in memory. -
DataStreamRequest: Encapsulates
URLSessionDataTask
and streams data from an HTTP connection over time. -
UploadRequest: Encapsulates
URLSessionUploadTask
and uploads data to a remote server. -
DownloadRequest: Encapsulates
URLSessionDownloadTask
by downloading response data to the disk.
Each request starts in an initialized
state. It can either be suspended
, resumed
or canceled
during its lifetime. The request ends in a finished
state.
Currently, you’re using a DataRequest to fetch your repositories. Now you’re going to intercept your requests using RequestInterceptor
.
RequestInterceptor Overview
Alamofire’s RequestInterceptor
consists of two protocols: RequestAdapter
and RequestRetrier
.
RequestAdapter
lets you inspect and mutate each request before sending it. This is ideal when every request includes an Authorization header.
RequestRetrier
retries a request that encountered an error.
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:)
. -
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.
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.