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.

5 (23) · 3 Reviews

Download materials
Save for later
Share
You are currently viewing page 2 of 3 of this article. Click here to view the first page.

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:

  1. The app makes a network request for authorization.
  2. Then, the user logs in to GitHub for the authorization to succeed.
  3. Next, GitHub redirects back to the app with a temporary code.
  4. The app requests an access token using that temporary code.
  5. 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.

GitHub OAuth Overview

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:

  1. Enter GitOnFire as the Application name.
  2. Enter https://www.raywenderlich.com/ as Homepage URL.
  3. Skip the Application description.
  4. Enter gitonfire:// as the Authorization callback URL.

Register GitHub OAuth applicatoin

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.

  1. You define the headers for the request. Accept with application/json tells the server the app wants the response in JSON format.
  2. Then you define the query parameters client_id, client_secret and code. These parameters are sent as part of the request.
  3. You make a network request to fetch the access token. The response is decoded to GitHubAccessToken. The TokenManager 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.

Oauth Login via Alamofire

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.

Access Token in 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:

  1. You define the URL to fetch your repositories.
  2. The per_page query parameter determines the maximum number of repositories returned per response. The maximum results you can get per page is 100.
  3. 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.

  1. You display a loading indicator before making a network request.
  2. Then, you make a network request to fetch your repositories.
  3. 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.

Empty List

The list is empty! Check the Xcode console, and you’ll see a 401 unauthorized request.

Alamofire 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 Requests.

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.

Alamofire Request Types

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.

  1. You declare two constants, retryLimit and retryDelay. They help enforce limits on the number of attempts for retrying a request and the duration between retry attempts.
  2. RequestAdapter is part of RequestInterceptor. It has a single requirement, adapt(_:for:completion:).
  3. 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 with RetryResult 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.

Logged in user repositories

Voilà! Now you'll see repositories fetched from your GitHub account.