Working with Alamofire in Swift

Written by LearnAppMaking on May 7 2021 in App Development, iOS, Swift

Working with Alamofire in Swift

Alamofire is an elegant networking library written in Swift. You use it to make HTTP requests in your iOS apps. In this tutorial, we’re going to discuss how you can use Alamofire for HTTP networking!

Here’s what we’ll get into:

  • How to make simple HTTP requests with Alamofire
  • Discuss the nitty gritty of JSON, URL-encoding and REST APIs
  • Convenient APIs for networking, for example with Codable
  • Handling responses of requests with Swift’s Result type
  • Different ways of providing a body to POST requests
  • How to handle errors in the request and response
  • Grabbing the data you need, like JSON, in the response handler
  • Working with HTTP headers like Authorization

Ready? Let’s go.

  1. What’s Alamofire?
  2. Install Alamofire in Your App
  3. GET Requests with Alamofire
  4. Making JSON GET Requests
  5. Handling Successful Responses with “.success(let data)”
  6. Handling Failed Responses with “.failed(let error)”
  7. Handling Responses with Decodable
  8. Adding GET Request Query Parameters
  9. Making POST Requests with Alamofire
  10. Setting POST Request Encoding
  11. Adding Request Parameters with Encodable
  12. Working with HTTP Headers
  13. Further Reading

What’s Alamofire?

Alamofire is a networking library written in Swift. You use it to make HTTP(S) requests on iOS, macOS and other Apple platforms. For example, to post data to a web-based REST API or to download an image from a webserver.

Alamofire has a convenient API built on top of URLSession (“URL Loading System”). Common use cases for networking include:

  • Get a collection of tweets, as JSON, from the Twitter API
  • Post some data to a “x-www-form-urlencoded” endpoint
  • Upload a photo from a user’s iPhone to “the cloud”
  • Authenticate your app’s user with a web-based REST API
  • Download a JPEG image and display it in an Image view

Because Alamofire is built on top of URLSession, it uses the same core concepts:

  • Session: A session is a shared resource for multiple requests. The session coordinates a group of data-transfer tasks. You can configure the session to store cookies, handle caching, or only perform in the background.
  • Task: A (data) task involves making a request and getting its response. Each data task belongs to a session (above). You can choose between a data task, upload task, download task, or a stream task.

An advantage of Alamofire over URLSession is that it’s often easier to use than URLSession. Everyday iOS development involves many typical tasks related to networking and HTTP requests; Alamofire provides convenient APIs and abstractions for those tasks.

A disadvantage of Alamofire, compared with URLSession, is that using URLSession is often good enough for simple networking requests. If you’re only going to post some JSON data to a URL, you might be better off by just using URLSession.

It’s worth noting that networking with Alamofire is completely asynchronous. Requests are handled off the main thread, which keeps the UI smooth and snappy. If you want to update the UI based on a request’s returned response, you’ll need to do so on the main thread.

Always use HTTPS! It’s strongly recommended you always use SSL/TLS, i.e. a URL that starts with https://, when performing networking tasks on iOS. Otherwise it’s incredibly easy to leak sensitive information or to change the HTTP response in flight (MITM). Thanks to App Transport Security (ATS), making plain HTTP requests is disabled by default.

Install Alamofire in Your App

Before you can use Alamofire, you’ll need to add the library to your Xcode project as a dependency. This will retrieve the library’s code from GitHub and adds it to your app.

You can install Alamofire in your project in a few ways:

  • Using the Swift Package Manager (SPM), which is integrated into Xcode and super convenient to use. (The simplest choice these days!)
  • With the 3rd-party package managers CocoaPods or Carthage. (Especially CocoaPods is a popular choice for existing production apps.)
  • Manually, by downloading Alamofire from GitHub or by adding it as a Git submodule, and adding Alamofire.framework manually to your project

First, make sure you’ve created a new app project in Xcode. You can also use your own project, of course!

Then, do this:

  1. Go to File → Swift Packages → Add Package Dependency…
  2. In the dialog that appears, input this URL: https://github.com/Alamofire/Alamofire.git and click Next
  3. Set Version to Up to Next Major (or what works for you!) and click Next
  4. When prompted, add the package Alamofire to your app project’s target (checked by default) and click Finish

Swift Package Manager now grabs Alamofire’s code from GitHub and automatically adds it to your Xcode project. According to the semantic versioning rules you set (major), Xcode will also automatically update the library with minor versions and patches.

Swift Package Manager screenshot in Xcode with Alamofire installed

Quick Tip: To try out the code from this tutorial, you can put it in a view (controller) or any part of the code that runs when the app starts. You can also use a Playground in Xcode.

GET Requests with Alamofire

Let’s take a look at a few Alamofire examples.

With the code below, from Alamofire’s official documentation, we’re making a HTTP GET request to https://httpbin.org/get.

AF.request("https://httpbin.org/get").response { response in
    debugPrint(response)
}

It’s a concise example, but there’s a lot to dig in here. Let’s go over each bit of code:

  • AF is a reference to Alamofire’s Session.default instance. You can use AF to quickly mock up example code, and use Session.default in your own (production) code. Its role is similar to that of URLSession.shared; it’s a central starting point for requests with minimal configuration.
  • The request() function creates a data task, i.e. a HTTP request. This function actually has a bunch of parameters, but in its most simple form it’ll take a string that contains a valid URL.
  • The result of the request() is chained to the response() function. The most important role of the response() function (and those like it) is to provide a completion handler. This is a closure that’s executed when the request completes.
  • The closure for response() has one parameter response of type AFDataResponse<Data?>. It’s a wrapper around the response type, containing information about the data that’s returned. The type is a generic whose data property can be nil.

Finally, the debugPrint() is a standard Swift function that’ll print out some data, including extra information. When you run the above code, you’ll see the following output:

[Request]: GET https://httpbin.org/get
    [Headers]: None
    [Body]: None
[Response]:
    [Status Code]: 200
    [Headers]:
        access-control-allow-credentials: true
        Access-Control-Allow-Origin: *
        Content-Length: 452
        Content-Type: application/json
        Date: Wed, 05 May 2021 12:42:18 GMT
        Server: gunicorn/19.9.0
    [Body]:
        {
          "args": {}, 
          "headers": {
            "Accept": "*/*", 
            "Accept-Encoding": "br;q=1.0, gzip;q=0.9, deflate;q=0.8", 
            "Accept-Language": "en;q=1.0, nl-US;q=0.9", 
            "Host": "httpbin.org", 
            "User-Agent": "AlamofireExample/1.0 (com.learnappmaking.AlamofireExample; build:1; iOS 14.5.0) Alamofire/5.4.3", 
          }, 
          "origin": "123.456.789.123", 
          "url": "https://httpbin.org/get"
        }
[Network Duration]: 1.0773659944534302s
[Serialization Duration]: 0.0s
[Result]: success(Optional(452 bytes))

In the above output you can see request headers, response headers, timing information, and more. Especially that last line is interesting: it’s Swift’s native Result enum, which contains the .success case with an associated Data? type.

This showcases Alamofire’s flexibility and composability, because you can quickly “get out of” the Alamofire ecosystem and into Swift’s standard types and components.

Tip: HTTPBin.org is a useful webservice to test and debug HTTP requests and responses. It has a bunch of endpoints you can try, that’ll return data like cookies, authentication, images and query parameters.

Making JSON GET Requests

Here’s an example of making a HTTP GET request with JSON:

let url = "···"

Session.default.request(url).responseJSON { response in

    switch response.result {
    case .success(let data):
        if let users = data as? [[String: Any]] {
            for user in users {
                print(user["first_name"] ?? "")
            }
        }
    case .failure(let error):
        print("Something went wrong: \(error)")
    }

    debugPrint(response.result)
}

In the above code we’re requesting the JSON from this users.json file.

It looks like this:

[
  {
    "first_name": "Ford",
    "last_name": "Prefect",
    "age": 5000
  },
  ···
]

In the above Swift code, we’ve chained the call to request() to the responseJSON() function. This function works exactly the same as the previous response() function, except that it’ll attempt to decode the returned JSON data using JSONSerialization.

In the completion handler, we’re using the switch statement to determine if response.result has succeeded or failed. This is Swift’s standard Result type, with associated values Any for .success and AFError for .failure.

Most web-based APIs use the JSON data format these days. JSON is a way of formatting data that can be processed quickly and is also easy to read for humans. A disadvantage of JSON is that it doesn’t enforce a schema.

Handling Successful Responses with “.success(let data)”

First, what happens when the request is successful? Check out this code:

switch response.result {
case .success(let data):
    if let users = data as? [[String: Any]] {
        for user in users {
            print(user["first_name"] ?? "")
        }
    }
case .failure(let error):
    print("Something went wrong: \(error)")
}

The .success case in the switch is triggered, which provides us with a data constant of type Any.

When you work with JSON in (plain) Swift, you always get an Any type that you need to type cast to an expected type yourself. Based on the users.json file, we can say with certainty that the type of Any is [[String: Any]]. An array of dictionaries with String typed keys and Any typed values.

Finally, we loop over the users array and print out the "first_name" value for each of the entries. For the sake of simplicitly, we’re quickly unwrapping the optional with the ?? nil-coalescing operator.

Handling Failed Responses with “.failed(let error)”

Then, what happens if the request fails? The .failure case has an associated value of type AFError, that we simply print out. At first glance, this AFError type just implements the error protocol.

Looking through the source code for AFError, however, we can see that it provides a few dozen reasons for why the request failed. You can get failure reasons for errors with validation, JSON encoding, serialization, and much more. This enables you to respond to specific errors, while ignoring others.

This is where Alamofire really shines. With URLSession, you’d have to work your way through numerous error systems, including data == nil, error != nil, response code checking, handling JSON errors with try-catch, and so on. Alamofire provides this data in an easy-to-handle .failure case.

Check out this example:

AF.request("https://httpbin.org/status/404")
    .validate(statusCode: 200..<300)
    .response { response in

    switch response.result {
    case .success:
        print("succes!")
    case .failure(let error):
        print(error)
        print(response.response?.statusCode)
    }
}

In the above code, we’re requesting a URL that’ll deliberately return a HTTP status code of 404 Not Found. The status code of the response is validated with validate(). We’re indicating that we’ll consider response codes in the 200-299 range as successful.

In the completion handler, we’re printing out the reponse’s status code, as well as the error. Here’s the output:

responseValidationFailed(reason: Alamofire.AFError.ResponseValidationFailureReason.unacceptableStatusCode(code: 404))
Optional(404)

Because Alamofire’s error handling is so extensive, we can specifically respond to the 404 Not Found error. Based on that, we can inform the user that some file or resource is missing. Neat!

Handling Responses with Decodable

A great approach to dealing with JSON in Swift is the Codable component. You use it to decode (and encode) JSON to native Swift types. You can define your own User struct, for example, and convert from/to JSON.

Alamofire has built-in support for Decodable. It can automatically decode a request’s response into objects from a certain type. Check this out:

struct User: Codable {
    let first_name: String
    let last_name: String
    let age: Int
}

let url = "···"

Session.default.request(url).responseDecodable(of: [User].self) { response in

    switch response.result {
    case .success(let users):
        print(users.map { "\($0.first_name) \($0.last_name)" })
    case .failure(let error):
        print(error)
    }
}

In the above code, we’ve defined a struct called User that conforms to the Codable protocol. It has String and Int properties, which work OK with Codable. The property names are identical to the data found in users.json.

Then, we’re making a request with Alamofire. This time we’re using the responseDecodable(of:completionHandler:) function. It has 2 parameters:

  1. What we want to decode: [User].self, which is the metatype for an array of User objects. That’s identical to the data types in the JSON file.
  2. The completion handler: a closure with its response parameter. This time, the .success case’s associated value is the decoded JSON with type [User].

Inside the .success case, we’re taking the decoded users array and transform it into an array of first/last name strings:

["Ford Prefect", "Zaphod Beeblebrox", "Arthur Dent", "Trillian Astra"]

Adding GET Request Query Parameters

What else is a common requirement for dealing with HTTP GET URLs? Query parameters, of course! They’re the encoded stuff after the question mark ? in a URL. Alamofire makes dealing with query parameters a cinch.

You’ve got 2 ways to put query parameters in a URL:

  1. Directly in the URL, i.e. http://httpbin.org/get?foo=bar&lorem=ipsum
  2. By providing an array to the parameters function parameter of request()

Check this out:

struct HTTPBinResponse: Codable {
    let args: [String: String]
}

AF.request("https://httpbin.org/get?foo=bar&lorem=ipsum").responseDecodable(of: HTTPBinResponse.self) { response in

    if case let .success(data) = response.result {
        print(data.args)
    }
}

// Output: ["foo": "bar", "lorem": "ipsum"]

In the above code, we’ve added 2 parameters to the request URL. These parameters are URL encoded, with ? and &. This is all there is to it: just add the query parameters to the URL of the HTTP GET request.

Because HTTPBin sends us the query parameters back unaltered, we can print them out in the response handler. In this case, we’ve used responseDecodable() again. We know that – in this case – the type of args is [String: String]. You can, of course, use any kind of response handler in your own code.

The second approach to add query parameters is useful in scenarios where these parameters come from some other part of your app, such as an array or dictionary that’s provided to the component that makes HTTP requests.

Here’s an example:

let params: [String: Any] = [
    "v": "20210505",
    "type": "venues",
    "page": Int.random(in: 0..<99)
]

AF.request("https://httpbin.org/get", parameters: params).responseDecodable(of: HTTPBinResponse.self) { response in

    if case let .success(data) = response.result {
        print(data.args)
    }
}

In the above code, we’ve prepared a dictionary params of type [String: Any]. Note that the array contains both string and integer values. We’re providing this params dictionary to the request(_:parameters:) function.

Alamofire will automatically encode parameters with URLEncoding.default, i.e. the URL encoding that’s common on the web. In the next section, when we’re going to deal with HTTP POST request, you’ll see that we can use different encoding types for the parameters of a request, including form encoding and JSON.

Quick Note: Keep in mind that, even when you’re using HTTPS, query parameters for GET request aren’t encrypted! Never add sensitive information to the URL or its query parameters when making GET requests.

Making POST Requests with Alamofire

HTTP POST requests are similar to GET requests, except that they typically include a request body with some data. You can compare that data to the query parameters a GET request includes in its URL, except now the data is encoded as JSON in the POST request body.

An example of a POST request is what happens when you submit a HTML form on a website. Every one of the form’s input field, dropdown lists and file uploads is sent to a webserver in a HTTP POST request.

In the typical REST API environment, like the Twitter API, you use POST requests to change a web-based resource. You use GET to merely “get” or download that resource. Just as before, JSON is a common data format for POST requests.

GET and POST are HTTP methods (also known as “verbs”). They are part of the HTTP protocol, that webservers and clients use to communicate with each other. REST is a common architecture for web-based APIs. It’s built on top of HTTP, and it defines a set of rules and tools to make stateful communication between a client and a webserver possible.

Making a POST Request

Let’s take a look at an example:

let params = [
    "first_name": "Arthur",
    "last_name": "Dent",
    "email": "[email protected]"
]

AF.request("https://httpbin.org/post", method: .post, parameters: params).responseJSON { response in
    debugPrint(response)
}

The above code is similar to what we’ve worked with before. You can see one difference: the .post argument for the method parameter. This instructs Alamofire to make a HTTP POST request, as opposed to the default GET.

Just as before, the params dictionary is sent as part of the HTTP request. It’s encoded as application/x-www-form-urlencoded, which is the default way of encoding HTML forms prior to sending them to a POST endpoint.

This is the raw HTTP POST request we’re sending to HTTPBin:

POST /post HTTP/1.1
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Host: httpbin.org
Connection: close
User-Agent: ···
Content-Length: 56

first_name=Arthur&last_name=Dent&email=arthur%40dent.com

See how the data from the params dictionary is added to the request? It’s the request’s body, URL-encoded. Neat!

This is what the HTTPBin webserver sends back as a response:

HTTP/1.1 200 OK
Date: Fri, 07 May 2021 11:39:07 GMT
Content-Type: application/json
Content-Length: 527
Connection: close
Server: gunicorn/19.9.0
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true

{
  "args": {}, 
  "data": "", 
  "files": {}, 
  "form": {
    "email": "[email protected]", 
    "first_name": "Arthur", 
    "last_name": "Dent"
  }, 
  "headers": {
    "Content-Length": "56", 
    "Content-Type": "application/x-www-form-urlencoded; charset=utf-8", 
    "Host": "httpbin.org", 
    "User-Agent": "...", 
  }, 
  "json": null, 
  "origin": "...", 
  "url": "https://httpbin.org/post"
}

The stuff between the squiggly brackets { } – the response body – is what we get back from the webserver. Just as before, HTTPBin tells us exactly what we’ve sent to the webserver in our own POST request.

Setting POST Request Encoding

So far, we’ve used URL-encoding for HTTP POST requests. Alamofire supports 2 formats, JSON and form URL-encoding, to send data with a POST request. But that’s not all…

First, let’s take a look at that POST request we made before:

AF.request("https://httpbin.org/post", method: .post, parameters: params).responseJSON { response in
    debugPrint(response)
}

Alamofire will encode params with URLEncodedFormParameterEncoder by default, as we’ve seen in the previous section.

You can also tell Alamofire to encode as JSON:

AF.request("https://httpbin.org/post", method: .post, parameters: params, encoder: JSONParameterEncoder.default).responseJSON { response in
    debugPrint(response)
}

See the encoder function parameter? Alamofire will automatically set the request’s Content-Type: application/json header. As expected, this is the HTTP POST request’s body, as JSON:

{"email":"[email protected]","first_name":"Arthur","last_name":"Dent"}

After inspecting the response we’re getting from HTTPBin, we can confirm that the data was in fact sent as JSON, and returned as JSON. Neat!

Adding Request Parameters with Encodable

One of the cool things Alamofire can do, is send a good ol’ struct as the body of a HTTP request. As long as it conforms to Encodable, you can put that object right into the request.

Check this out:

struct LoginData: Codable {
    let username: String
    let password: String
}

let data = LoginData(username: "[email protected]", password: "iluvtrillian")

AF.request("https://httpbin.org/post", method: .post, parameters: data, encoder: JSONParameterEncoder.default).responseJSON { response in
    debugPrint(response)
}

Neat, right? You’ve got that LoginData struct that conforms to Codable (Encodable and Decodable), the data object that contains login data for the request, and the request itself that takes data for its parameters parameter.

This is again an exceptional display of the convenience that the Alamofire library offers. You can make requests with Encodable structs, and automatically decode responses with the responseDecodable() handler.

This enables you to structure your request/response data around Codable and JSON, and saves you from a whole lot of boilerplate code. The encoding and formatting gets out of the way, so you can focus on the data you’re sending and receiving. Awesome!

Technically, Codable is format-agnostic. You can use it with JSON out-of-the-box, but you can also serialize/unserialize your own data formats, including URL-encoding and even XML.

Working with HTTP Headers

Last but not least: HTTP headers! What are they and what do you use them for?

A HTTP header is a bit of text that’s sent as part of an HTTP request. You use it to provide metadata about the request (or response), such as the response type, encoding, length, date/time, and more.

Here’s an example:

Content-Type: application/json

A header consists of a string key and value, separated by a colon: . Kind of like a dictionary in Swift!

The above header tells the recipient of the HTTP request that the content type for this request is JSON; its body consists of a JSON string. The responding webserver can use this information to parse the HTTP body effectively, because it doesn’t have to assume or work out on its own what the formatting is.

Headers are more or less standardized. You’ll find common headers like Content-Type, Content-Length, Accept, User-Agent in apps and web browsers. Custom headers usually start with X- or another prefix belong to specialized applications and platforms, such as Amazon (amz-) or Cloudflare (CF-).

HTTP headers also play an important role in authentication and authorization. For example, if you’ve logged into the Twitter iOS app, requests made by the app contain some metadata that authorizes you as the user of the app. That’s how Twitter knows that it should post a new tweet to your timeline.

Alamofire will automatically attach certain headers to requests. You can also add your own headers to the HTTP request. Here’s an example:

let headers: HTTPHeaders = [
    "Authorization": "Basic am9obmRvZTphYmNkMTIzNA=="
]

AF.request("https://httpbin.org/basic-auth/johndoe/abcd1234", headers: headers).responseJSON { response in
    debugPrint(response)
}

In the above code we’ve added a headers object to the headers parameter of the request() function – completely what you’d expect from Alamofire right now. Let’s deconstruct that for a bit!

First off, the header we’re sending is this:

"Authorization: Basic am9obmRvZTphYmNkMTIzNA=="

This Authorization header consists of a keyword, like Basic, and a base64-encoded string. This string is johndoe:abcd1234, which is the username and password we’re using to authenticate ourselves with the webserver.

HTTPBin will check if the username and password match what we’ve provided in the URL query string, which is a great way to test and debug if your app is authorizing itself OK.

The type of the headers constant is HTTPHeaders, which belongs to Alamofire, of course. The HTTPHeaders is a struct – and pretty cool – because the struct can be represented by an array of dictionary literal. That’s why we can assign a simple dictionary in the above code! Convenient, right?

The HTTPHeaders type also preserves the order of HTTP headers, which is helpful in a request context. It also contains a bunch of extensions for common HTTP headers, so you can add strongly-typed headers directly in Swift.

Check this out:

let headers: HTTPHeaders = [
    .authorization(username: "johndoe", password: "abcd1234"),
    .accept("application/json")
]

AF.request(···, headers: headers).responseJSON { ···

In the above headers object, we’ve added a bunch of (static) function calls that represent the Authorization and Accept headers. We can rely on Alamofire to do the heavy-lifting for us, and do some magic to add default headers to requests, like .userAgent(_:) and .contentType(_:). Neat!

Further Reading

Pfew! We’ve taken quite the tour around the web with Alamofire. From HTTP GET and POST, to JSON and URL-encoding, to HTTP headers and response completion handlers, we’ve discussed in-depth how you can get started with Alamofire in your own app projects.

Alamofire is one of the best examples of a well thought out 3rd-party library. It gets out of your way, and at the same time, provides APIs for HTTP requests that are convenient and make sense. Even though Alamofire serves the same end goal as URLSession, it’s much more convenient and certainly easier to use.

A few parting notes:

  • Check out the official documentation for Alamofire right here.
  • HTTPBin.org is a great tool for HTTP APIs, as are the Paw and Charles apps for Mac.
  • Don’t forget to read up on URLSession. It’s the foundation that Alamofire is built upon, so don’t miss out on learning that too.

Want to learn more? Check out these resources:

LearnAppMaking

LearnAppMaking

At LearnAppMaking.com, app developers learn how to build and launch awesome iOS apps.