How Do Alamofire Routers Work?
April 06, 2016

Sometimes we read tutorials or books and the code seems like magic. It works but it’s really not clear why. Especially when it’s full of weird Swift stuff like enums and computed properties and pattern matching. It’s enough to make you want to curl up in bed and give up on programming altogether.

Pug curled up in blanket in bed
How “magic” code makes me feel (image by Matthew Wiebe)

A reader pointed out recently that my Alamofire router code is guilty of showing fancy code with funky Swift features. And the blog post doesn’t make it clear what’s happening. So today I’ll make things right and we’ll figure out exactly how something like Alamofire.request(TodoRouter.get(1)).responseJSON… actually works.

Here’s the question I was asked:

In the Router, you use case get(Int) but I never see func asURLRequest() actually get called. All I see is Alamofire.request(Router.get(1)).responseJSON.... How does the computed property get called?

In case you don’t have a photographic memory, here’s how we declared the router:

enum TodoRouter: URLRequestConvertible {
  static let baseURLString: String = "https://jsonplaceholder.typicode.com/"
  
  case get(Int)
  case create([String: Any])
  case delete(Int)

  func asURLRequest() throws -> URLRequest {
    // create and return the URL request
  }
}

The call using it that we’re trying to figure out looks like:

let request = Alamofire.request(TodoRouter.get(1))
  .responseJSON { response in
    // ... do stuff with the response
  }

For a whole demo project, go to GitHub.

The question is: How does Alamofire.request(TodoRouter.get(1)) end up calling TodoRouter.TodoRouter.get(1)? It doesn’t appear anywhere in our code!

Let’s decompose this line: Alamofire.request(TodoRouter.get(1)), working from the inside out.

TodoRouter.get(1) just returns an instance of our enum for that case. So this would be the same as the line we’re trying to figure out:

let enumCase: TodoRouter = TodoRouter.get(1)
Alamofire.request(enumCase)

That’s similar to calling an initializer to create an instance of a class. If we set up test code to just call TodoRouter.get(1) (comment out Alamofire.request(enumCase)) and put a break point in the var URLRequest block, then the URLRequest computed property never gets called. So TodoRouter.get(1) isn’t what’s causing the getter for URLRequest to get called.

URLRequestConvertible

So it must be Alamofire.request(enumCase) that’s making the call to asURLRequest() happen. Alamofire.request() will recognize that our enum is URLRequestConvertible so it has a asURLRequest() function, as required by that protocol. That’s because when we created our router we declared that each case would be a URLRequestConvertible:

enum TodoRouter: URLRequestConvertible

What’s a URLRequestConvertible? Cmd-click on it to see it’s declaration! According to the Alamofire source code, URLRequestConvertible is a protocol. The only requirement for the protocol is that it has a read-only computed var property that is an NSMutableURLRequest:

/// Types adopting the `URLRequestConvertible` protocol can be used to construct URL requests.
public protocol URLRequestConvertible {
  /// Returns a URL request or throws if an `Error` was encountered.
  ///
  /// - throws: An `Error` if the underlying `URLRequest` is `nil`.
  ///
  /// - returns: A URL request.
  func asURLRequest() throws -> URLRequest
}

So when we give Alamofire.request() an enum like TodoRouter.get(1) then it can call asURLRequest() to get the request to send.

If you’re wondering where this happens, cmd-click .request(...) in Xcode code. It’ll show you the declaration for that function:


@discardableResult
public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
    return SessionManager.default.request(urlRequest)
}

Nothing exciting there… So cmd-click on request(urlRequest) in the body of that function to see what gets done with it:


open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {
  var originalRequest: URLRequest?

  do {
    originalRequest = try urlRequest.asURLRequest()
    // ... create a task for the request, add it to a queue, and start it
    return request
  } catch {
    return request(originalRequest, failedWith: error)
  }
}

Right on the first line in that function it takes the urlRequest, in our case Todo.get(1), and calls .asURLRequest() on it. So that’s how the router works with Alamofire.request(...).

How It Works

So here’s what happens:

  1. We create a router enum case for the API call we want to make. It must be a URLRequestConvertible
  2. Alamofire.request(...) takes that URLRequestConvertible and gets the URL request from it using asURLRequest()
  3. Alamofire uses that request to make the network call
  4. Any response handlers attached like Alamofire.request(...).responseJSON { ...} get called when the network call gets results (or times out or whatever)

While we’re using an enum, you don’t necessarily have to. You can use anything that conforms with URLRequestConvertible (which just means it has a asURLRequest() function) to make an Alamofire request. Classes, structs, or enums can be used. For example, here’s a struct that implements URLRequestConvertible:

struct GetPostURLRequestProvider: URLRequestConvertible {
  let idNumber: Int
  
  init(idNumber: Int) {
    self.idNumber = idNumber
  }
  
  func asURLRequest() throws -> URLRequest {
    // don't use !'s (except for super-quick demos that will never be deployed)
    return URLRequest(url: URL(string: "https://jsonplaceholder.typicode.com/posts/\(idNumber)")!)
  }
}

And you can use it to make an Alamofire request:


let urlRequestProvider = GetPostURLRequestProvider(idNumber: 1)
  Alamofire.request(urlRequestProvider)
    .responseJSON { ... }

And That’s All

Does that seem a little less magical now? Hopefully we’ve pulled back the curtain and demystified some of the “magic” that was in the last tutorial. As always, if you have any questions about anything in these tutorials please ask by email or comment below. If you’re stuck then plenty of other people probably are too.

If you’d like more Swift tutorials on topics like this one, sign up below to get them sent directly to your inbox.