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.
- What’s Alamofire?
- Install Alamofire in Your App
- GET Requests with Alamofire
- Making JSON GET Requests
- Handling Successful Responses with “.success(let data)”
- Handling Failed Responses with “.failed(let error)”
- Handling Responses with Decodable
- Adding GET Request Query Parameters
- Making POST Requests with Alamofire
- Setting POST Request Encoding
- Adding Request Parameters with Encodable
- Working with HTTP Headers
- 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:
- Go to
- In the dialog that appears, input this URL:
https://github.com/Alamofire/Alamofire.git
and click Next - Set Version to Up to Next Major (or what works for you!) and click Next
- 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.
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’sSession.default
instance. You can useAF
to quickly mock up example code, and useSession.default
in your own (production) code. Its role is similar to that ofURLSession.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 theresponse()
function. The most important role of theresponse()
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 parameterresponse
of typeAFDataResponse<Data?>
. It’s a wrapper around the response type, containing information about the data that’s returned. The type is a generic whosedata
property can benil
.
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)
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:
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:
- What we want to decode:
[User].self
, which is the metatype for an array ofUser
objects. That’s identical to the data types in the JSON file. - 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:
- Directly in the URL, i.e.
http://httpbin.org/get?foo=bar&lorem=ipsum
- By providing an array to the
parameters
function parameter ofrequest()
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:
“`swift
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:
```swift
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:
- Networking in Swift with URLSession
- Working with Codable and JSON in Swift
- Working With JSON In Swift With SwiftyJSON
- Working with Files on iOS with Swift
- Promises in Swift
- Dictionaries in Swift Explained
- Using CocoaPods With Xcode Playground
- Fun with print() in Swift
- How To: Xcode 12 Tutorial for Beginners
- Get Started with Swift Package Manager (SPM)
- Get Started with Xcode Playgrounds