An Introduction to Alamofire 5: from CRUD to Authentication
Sample Xcode project by Ziad Hamdieh
As an iOS engineer who has to write and maintain a lot of networking code, I often opt for using the Alamofire networking library (which, under the hood, uses URLSession) over using URLSession directly. This is unless I need to write a lightweight library with no external dependencies, which typically is not the case. In fact, at TextNow we decided to become early adopters of Alamofire 5. Because the public API for Alamofire 5 has changed significantly from the previous version, we thought an article on how to use Alamofire 5 might be of interest to fellow iOS developers.
When it comes to Alamofire 5, there are two fundamental uses. The first is basic: how to make HTTP calls against a REST API. The second, more advanced usage, might involve adding content to the header of your HTTP request for each and every outgoing call and do so in a way that does not have you repeat yourself. This article will show you how to use Alamofire 5 for both your basic and advanced needs. We even included an Alamofire.Session
extension that you can copy-and-paste to help you get off the ground quickly. So let’s get started!
1. The basics: two ways to send data
Consider this example: you need to tell your backend that the name of a particular mobile user is “Jonathan” and that Jonathan’s age is 65.
There are two obvious ways to send this data to your backend. The first way is as a URL-encoded query string. The second is in the body of the request in JSON format.
Query string vs. JSON
As a query string, it might look like this:
name=jonathan&age=65
If our base URL is http://mybackend.com
, our endpoint is /endpoint/<username>
, and the user’s username is clarksdad
, then the entire URL would be:
http://mybackend.com/myendpoint/users/clarksdad?name=jonathan&age=65
As JSON, this same data would look like this:
The name and age of the user might differ each time, but that’s what a sample JSON payload looks like with the keys “Name” and “Age” staying constant. This payload would then get sent in the body of the request, leaving the URL to be simply: http://mybackend.com/myendpoint/users/clarksdad
Modeling the data
Whether sending JSON or a query string, our data model must conform to Encodable
. In Swift, dictionaries conform to Encodable
, so you could use a dictionary. Our data model would then look as follows:
A particular user would then look like this:
Or we could use a struct. Keys in dictionaries are strings and so dictionaries in this sense have weaker typing and so, all other things being equal, you might prefer structs. The struct would need to conform to either Codable
or Encodable
. An Encodable
is all we really need because our goal is to send it, not receive it.
Alamofire 5 accepts an Encodable
and so, whether you are sending a query string or a JSON payload, both dictionaries and structs will work. Using a struct, a particular user would then look as follows:
Sending the data
With Alamofire 5, we could send our user data by setting the parameters
and encoder
fields of the request
method as follows. Sending a dictionary as URL encoded, for example, would look as follows:
Sending the same user data as JSON looks very similar:
As the code snippets above illustrate, the encoder
parameter (in the request
method) can be set to either URLEncodedFormParameterEncoder.default
or JSONParameterEncoder.default
, depending on whether you need URL encoding or JSON format. The parameters
parameter, admittedly a somewhat confusing name, is where our user data goes to be encoded and sent. The parameters
field is very powerful because it can accept a dictionary or a Codable
/Encodable
struct. It can accept both because both are Encodables.
To recap, with Alamofire 5’s public API, sending a JSON body is not much different than sending a query string and both can be sent using a struct (or a dictionary).
2. Alamofire’s missing CRUD API
Warning: This section has caused a bit of controversy, so read at your own risk! See the Responses section below this blog post.
Wouldn’t it be nice if, instead of the above request
calls, we could simply do the following:
Or, in the case of a POST:
Handling the response would still look the same as in the previous section — we are not looking to change that part. For example, in the case of a GET request:
That way, not only is there less to type, but we wouldn’t have to worry about accidentally ending up with a combination of function arguments that don’t make sense. In other words, wouldn’t it be nice if Alamofire had a CRUD API for common REST calls? Well, it could!
CRUD
The four basic operations required for persistent storage are create, read, update, and delete — CRUD for short. REST and SQL, for example, both support basic CRUD operations. Relational databases typically rely on SQL (“structured query language”) for interacting with and managing the data they store. Using SQL, we can create a new record (INSERT), read an existing record (SELECT), update a record (UPDATE), and delete a record (DELETE). In other words, SQL gives us the four basic CRUD operations. And so does REST.
A subset of the HTTP protocol, or more specifically a subset of HTTP methods, similarly gives us CRUD operations for REST and RESTful APIs. There is a create operation (POST), a read operation (GET), an update operation (PUT/PATCH), and a delete operation (DELETE). We could extend Alamofire’s Session
class to have a CRUD API for REST by taking the two basic calls we cooked up in the previous section and treating them as building blocks. The following Alamofire extension, which you can copy-and-paste into your Xcode project and then massage to fit your backend, simplifies call sites by supporting all four basic CRUD operations along with PUT’s smaller cousin, PATCH. Here is the code you would need:
That simple extension gives Alamofire a more user-friendly API and covers most use cases. If your use case is slightly different, you can massage the code above to fit. (For example, the code above presupposes that all of your data to be sent as query strings is supplied using dictionaries while all of your data to be sent as JSON in the body of a request is supplied using structs.)
It is true, however, that the typical way to support CRUD endpoints in production using Alamofire today is to create an enum which acts as a collection of endpoints or HTTP methods to be exercised on an endpoint. You could even have one giant enum which houses all your endpoints in one place and so acts as a router. Either way, this enum would then have a computedmethod
property of type HTTPMethod
. For an example, see the Alamofire ‘advanced usage’ documentation.
Keeping this in mind, turning CRUD operations into functions — turning HTTP methods into Swift methods — might be seen as a bit of a throwback, perhaps brought about by personal nostalgia. Both are true: it is a throwback and it is not without nostalgia. However, I believe that in certain cases this can be appropriate. Firstly, it is intuitive to think of GETs and POSTs as methods. Secondly, unlike an enum-based router, it does not fall under advanced usage and does not require the level of comfort with Swift that the enum way requires— thinking of enums as similar to classes and structs, as something that can have computed properties and methods and, moreover, can switch on self
to return the property value appropriate to the enum case. Thirdly, you might actually wish to limit the types of HTTP calls (e.g. in a large codebase maintained by a team rather than by you alone when the backend supports a well-defined limited set). For example, you might wish to eliminate the possibility of a GET with a body or a POST without one and perhaps completely disallow PATCH or PUT. Moreover, these three reasons aside, in a more advanced architecture, it is actually also possible to use the two approaches in tandem.
Whether this approach will scale, assuming you even need it to, might depend on whether you need to keep your options open or instead need to lock down a narrower set of HTTP calls.
3. Modifying the HTTP headers for all outgoing calls
Modifying an HTTP header in Alamofire is easy enough. For example:
request.headers.update(.authorization(bearerToken: token))
But if you need to append some content to HTTP headers of all outgoing requests, which is not uncommon, Alamofire can help with that, too. Typically, such content identifies the type of client (e.g. iOS vs. Android vs. web) and identifies the user (e.g. an authentication token), but could really be anything you need to add to the http header.
Essentially, whether by updating the headers of individual requests or by using an adapter, headers are a third way to send data in addition to the two ways we saw above (that is, in the request body or as a query string). After all, if an HTTP request can have a body, then it is not surprising that it can also have a head(er). Though I suppose when it comes to HTTP requests, when things get weird, it is more a case of the head of Professor Dowell than the Headless Horseman: all requests have headers, whereas not all have bodies (e.g. a GET is bodiless). But I digress.
A request adapter modifies the HTTP header for outgoing requests. The idea behind a request adapter is to write this piece of logic once, in a centralized location, so that we do not have to worry about it when making the kinds of network calls we were making above.
Imagine your backend requires that you send an “Authorization” HTTP header field with each and every request. HTTP header fields are key-value pairs (e.g. try it out in Postman) and so our key, conventionally, would then be “Authorization” (why the convention for authentication is called “authorization” — I am not sure). Moreover, your backend colleagues give you the following format requirement for the value of this key:
Authorization: Basic <token>
For example:
Authorization: Basic b3BlbiBzZXNhbWU=
(Try decoding the b3BlbiBzZXNhbWU=
using a base64 decoder; and for a fun “do-along” article on security, see here.) Because your backend is using this to authenticate users, the backend server will refuse to talk to you on any route if you don’t send this string. One elegant way to solve this problem is to intercept all outgoing call and have a request adapter “adapt” (that is, modify) each outgoing call by adding the “Authorization: Basic . . .” string to the HTTP header of each intercepted request.
For example, here is an implementation of a request adapter using Alamofire:
Then, unlike before, instantiate your Alamofire network session like so:
Voila! All your outgoing HTTP requests will now have the authorization header automatically appended, including the ones in the previous section, so long as they use the Alamofire session
we instantiated with our request adapter above.
What did we learn?
Alamofire 5 is a powerful networking library that can help with all kinds of use cases: everything from basic CRUD operations to modifying HTTP headers, sending data in the body of a request to centralizing session management with a request adapter. In this article I showed how to make simple GET and POST calls using this latest version of Alamofire (section 1), how to extend Alamofire to have a CRUD API (the controversial section 2), and how to write a request adapter (section 3).
Happy coding!
Sample Xcode project by Ziad Hamdieh