Alamofire is one of the most popular and widely used Swift networking libraries. Built on Apple’s Foundation networking stack, it provides an elegant API to make network requests. With over thirty-thousand stars on GitHub, it’s one of the top-rated Swift repositories.
Today you’ll dive in and master it. :]
In this tutorial, you’ll get an introduction to the advanced use of Alamofire. You’ll apply these concepts while building a GitHub client app, GitOnFire. Along the way, you’ll learn how to:
- Handle authentication using OAuth.
- Log network requests and responses.
- Retry network requests.
- Check for network reachability.
- Cache the network responses.
Here’s a fun fact: Alamofire got its name from Alamo Fire flower, one of the official state flowers of Texas. :]
Getting Started
Download the starter project by clicking the Download Materials button at the top or bottom of this page.
You’ll work on an app called GitOnFire. The starter project comes with the Alamofire network library and a basic integration of GitHub API.
Open the project in the starter folder. Build and run.
You’ll see a list of the most popular Swift repositories in GitHub. Notice Alamofire’s position on that list. :]
Specify a search-text to get the related list of popular repositories. Tap a repository’s name to see its recent commits.
You’ll update the network calls to use a custom URLSessionConfiguration. You’ll also integrate OAuth login and fetch repositories from your account.
These are the files you’ll work on first:
- GitAPIManager.swift: Fetches the repositories and commits from GitHub using Alamofire.
- RepositoriesViewController.swift: Displays the list of repositories.
- LoginViewController.swift: Handles GitHub OAuth login.
In this tutorial, you’ll use GitHub APIs which deal with OAuth authentication, fetching repositories and commits. For an extensive list of all the GitHub APIs and their uses, please refer to this API documentation.
Custom Session and URLSessionConfiguration
Open GitAPIManager.swift and find searchRepositories(query:completion:). The implementation for searching GitHub repositories is already in place. You’ll use this opportunity to understand the inner workings within the method.
func searchRepositories( query: String, completion: @escaping ([Repository]) -> Void) { // 1 let url = "https://api.github.com/search/repositories" // 2 var queryParameters: [String: Any] = [ "sort": "stars", "order": "desc", "page": 1 ] queryParameters["q"] = query // 3 AF.request(url, parameters: queryParameters).responseDecodable( of: Repositories.self) { response in guard let items = response.value else { return completion([]) } completion(items.items) }}
Here’s a step-by-step breakdown:
- You define the URL to search repositories.
- Then, you create an array of query parameters to fetch the repositories. You specify a descending sort order based on the number of stars for the repository.
- Next, you make the network request using Alamofire’s default session AF. The method decodes the received response and returns it in a completion block as an array of Repository, your custom model.
The default timeout for each request is 60 seconds. To update the timeout interval to 30 seconds, you can specify a requestModifier as part of the request, as shown below as reference:
AF.request(url, parameters: queryParameters) { urlRequest in urlRequest.timeoutInterval = 30}
You can specify requestModifier as a trailing block when constructing the request. But, what do you do if all the requests for a session need timeout intervals? You use a custom URLSessionConfiguration.
Customizing Session
Alamofire provides Session, which is similar to URLSession in terms of responsibility. It helps create and manage different requests and provides common functionality for all requests, such as interception, response caching and more. You’ll learn more about different types of requests later in this tutorial.
You can customize session behavior by using a URLSessionConfiguration with the desired configuration. Add the following code to GitAPIManager:
//1let sessionManager: Session = { //2 let configuration = URLSessionConfiguration.af.default //3 configuration.timeoutIntervalForRequest = 30 //4 return Session(configuration: configuration)}()
Here you:
- Define sessionManager as a custom Session.
- Then create an instance of URLSessionConfiguration from URLSessionConfiguration.af.default. The default Alamofire URLSessionConfiguration adds Accept-Encoding, Accept-Language and User-Agent headers.
- Set timeoutIntervalForRequest on the configuration to 30 seconds, which applies to all the requests in that session.
- Once you’ve defined the configuration, you create an instance of a Session by passing in the custom configuration.
Now you’ll use this sessionManager for all your network requests. In searchRepositories(query:completion:), replace:
AF.request(url, parameters: queryParameters)
With:
sessionManager.request(url, parameters: queryParameters)
Here, you make the request with sessionManager instead of AF.
Next in fetchCommits(for:completion:), replace:
AF.request(url)
With:
sessionManager.request(url)
Now both network requests have the request timeout interval set to 30 seconds.
Next, you’ll see the advantage of using custom sessions.
In sessionManager, add the following code below configuration.timeoutIntervalForRequest = 30:
configuration.waitsForConnectivity = true
Here, you set waitsForConnectivity to true which makes the session wait for network connectivity before making the request. You must set all custom configurations for URLSessionConfiguration before adding them to Session. If you mutate the configuration properties after adding to Session they won’t have any effect.
To see the code in action. Turn the WiFi off. Build and run.
The app loads, showing an empty list with a loading indicator. Then, turn the WiFi on. Within a few seconds, the repositories will load. Doesn’t it feel like magic?
Later on in this tutorial, you’ll learn how to monitor and handle network reachability.
Next, you’ll learn how to log network requests and responses using Event Monitor.
Logging Network Requests and Responses Using Event Monitor
So far, you’ve made network requests to fetch repositories and commits and displayed the results in a table view. That’s cool, but you can go further and see the raw network requests and responses.
Alamofire provides a powerful way to gain insight into all the internal events via the EventMonitor protocol. EventMonitor includes several Alamofire events such as URLSessionDelegate request events. This makes EventMonitor ideal for logging events.
Create a new Swift file named GitNetworkLogger.swift in Networking group. Add the following code:
import Alamofireclass GitNetworkLogger: EventMonitor { //1 let queue = DispatchQueue(label: "com.raywenderlich.gitonfire.networklogger") //2 func requestDidFinish(_ request: Request) { print(request.description) } //3 func request( _ request: DataRequest, didParseResponse response: DataResponse ) { guard let data = response.data else { return } if let json = try? JSONSerialization .jsonObject(with: data, options: .mutableContainers) { print(json) } }}
Here’s a code breakdown:
- EventMonitor requires a DispatchQueue which dispatches all events. By default, EventMonitor uses the main queue.You initialize queue with a custom DispatchQueue to maintain performance. It’s a serial queue that handles all the events for the session.
- requestDidFinish(_:) is called when the request finishes. Then, you print the description of the request to see the HTTP method and the request URL in the console.
- request(_:didParseResponse:) is called when the response is received. Using JSONSerialization, you render the response as JSON and then print it to the console.
Open GitAPIManager.swift. Add the following code below configuration.waitsForConnectivity = true in sessionManager:
let networkLogger = GitNetworkLogger()
Here, you define networkLogger as an instance of GitNetworkLogger.
Now replace return Session(configuration: configuration) with the following:
return Session(configuration: configuration, eventMonitors: [networkLogger])
The networkLogger is passed in an array to eventMonitors during Session initialization. Build and run.
Now you’ll see all the network requests and responses logged in the console. Great job!
So far, you’ve fetched public repositories. Now it’s time to fetch repositories from your own GitHub account. Get ready for some authorization fun. :]
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:
- You define the headers for the request. Accept with application/json tells the server the app wants the response in JSON format.
- Then you define the query parameters client_id, client_secret and code. 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. The TokenManager utility class helps store the token in the keychain.To learn more about using keychain and storing secure information, read this KeyChain Services API Tutorial for Passwords in Swift.
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:
//1loadingIndicator.startAnimating()//2GitAPIManager.shared.fetchUserRepositories { [self] repositories in //3 self.repositories = repositories loadingIndicator.stopAnimating() tableView.reloadData()}
Here’s a code breakdown:
- 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.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.
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 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.
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 Alamofireclass GitRequestInterceptor: RequestInterceptor { //1 let retryLimit = 5 let retryDelay: TimeInterval = 10 //2 func adapt( _ urlRequest: URLRequest, for session: Session, completion: @escaping (Result) -> 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:
- 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.
- RequestAdapter is part of RequestInterceptor. 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 with RetryResult 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 within retryLimit.
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:
//1extension 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 to URLRequestConvertible. The protocol has a single requirement, asURLRequest(), which helps construct a URLRequest. Conforming to URLRequestConvertible 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 or JSONParameterEncoder. 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 URLRequests 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:
// 1let reachabilityManager = NetworkReachabilityManager(host: "www.google.com")// 2func 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 of NetworkReachabilityManager. 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!
Note: You should test functionality related to network reachability on a real device because reachability might not work as expected on a simulator. You can learn more about the issue by reading this GitHub post.
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. :]
Caching Using ResponseCacher
Open GitAPIManager.swift. Remove the following configuration options in sessionManager:
configuration.timeoutIntervalForRequest = 30configuration.waitsForConnectivity = true
Here, you remove the timeoutIntervalForRequest and waitsForConnectivity configuration options so the app doesn’t wait for network connectivity.
Add the following below let configuration = URLSessionConfiguration.af.default in sessionManager:
//1configuration.requestCachePolicy = .returnCacheDataElseLoad//2let responseCacher = ResponseCacher(behavior: .modify { _, response in let userInfo = ["date": Date()] return CachedURLResponse( response: response.response, data: response.data, userInfo: userInfo, storagePolicy: .allowed)})
Here’s a breakdown:
- To cache requests for the session, you set requestCachePolicy on URLSessionConfiguration to returnCacheDataElseLoad. Once set, the cache returns the response. If the cache doesn’t have a response, a network request is made.
- Alamofire’s ResponseCacher makes it easy to specify whether a request needs to get cached, not cached or modified before storing in the cache. Here you modify the response by specifying .modify, before saving into the cache. You save the response’s date, in addition to the content of the response, by passing it in the userInfo dictionary.
Now update Session initialization in sessionManager as below:
return Session( configuration: configuration, interceptor: interceptor, cachedResponseHandler: responseCacher, eventMonitors: [networkLogger])
Here you pass responseCacher as cachedResponseHandler in the constructor of Session. This makes responseCacher handle the caching behavior for all the requests in Session.
Open AppDelegate.swift. Comment out the following line of code which starts network monitoring:
GitNetworkReachability.shared.startNetworkMonitoring()
This prevents the app from showing the No Network alert when it’s offline. Turn off network access on the simulator or device. Build and run.
Ta-da! You’ll see the repositories loaded from the cache.
Congratulations! You’re now an Alamofire pro. :]
Where to Go From Here?
Download the final project by clicking the Download Materials button at the top or bottom of the tutorial.
In this Alamofire tutorial, you learned how to:
- Create a custom Session and URLSessionConfiguration.
- Log network requests and responses using EventMonitor.
- Handle authentication and retries using RequestInterceptor.
- Configure a Router with URLRequestConvertible.
- Check for network reachability using NetworkReachabilityManager.
- Modify responses before caching using ResponseCacher.
To learn more, please check the Alamofire advanced usage documentation.
I hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below.
Download Materials
raywenderlich.com Weekly
The raywenderlich.com newsletter is the easiest way to stay up-to-date on everything you need to know as a mobile developer.
Get a weekly digest of our tutorials and courses, and receive a free in-depth email course as a bonus!