Fabian Buentello

With any project, whether it’s written in Swift, Objective-C, python, Ruby, etc. A big plus for all developers is to quickly learn about as much of the project without spending too much time going through each file.

I feel a router does just that, it should act as a roadmap to an application. It shouldn’t give you every detail of an application, but give you enough information for you to infer the following:

API endpoints the application uses

What models exist in an application

What HTTP method each model has. Example: .get, .post

Setup project

I built a basic application that will make a network call using Alamofire. To focus strictly on the Router portion of the application, we won’t implement any models.

We will be making calls to a mock server I made using Apiary, you should check it out if you haven’t already.

Let’s clone the repo, so we can start building out our router.

git clone https://github.com/chaione/RoutableApp

Before building a router using protocols we need to build our router using today’s common approaches.

Building Router using Alamofire Router

To follow along with this tutorial, run the following command:

git checkout chapter_1_starting

So we can actually make real API calls, we’re going to be using Apiary to host a mock server, you should check it out if you haven’t already.

In my first attempt in building a Router, I was using Alamofire’s Router.
To get a better idea of how this would all work, let’s code. Let’s create a Router.swift file.

// Router.swift importAlamofireenumRouter:URLRequestConvertible{casereadUserscasecreateUser(parameters:Parameters)casereadUser(username:String)caseupdateUser(username:String,parameters:Parameters)casedestroyUser(username:String)// Using a fake URL to get datastaticletbaseURLString="https://private-85a46-routable.apiary-mock.com"varmethod:HTTPMethod{switchself{case.readUsers:return.getcase.createUser:return.postcase.readUser:return.getcase.updateUser:return.putcase.destroyUser:return.delete}}varpath:String{switchself{case.readUsers:return"/users"case.createUser:return"/users"case.readUser(letusername):return"/users/\(username)"case.updateUser(letusername,_):return"/users/\(username)"case.destroyUser(letusername):return"/users/\(username)"}}// MARK: URLRequestConvertiblefuncasURLRequest()throws->URLRequest{leturl=tryRouter.baseURLString.asURL()varurlRequest=URLRequest(url:url.appendingPathComponent(path))urlRequest.httpMethod=method.rawValueswitchself{case.createUser(letparameters):urlRequest=tryURLEncoding.default.encode(urlRequest,with:parameters)case.updateUser(_,letparameters):urlRequest=tryURLEncoding.default.encode(urlRequest,with:parameters)default:break}returnurlRequest}}

Oh No! No! No! I’m not loving this solution at all. This file seems to grow vertically extremely fast and we haven’t even gotten to the relationships between the routes… need a new solution. Let’s check out a popular library Moya.

Moya

After looking into Moya’s documentation and finding their basic example code, it seems like we’ll run into the same “growing vertical” issue. Moya definitely provides a lot of solutions, just not the solution for my problem. It definitely bums me out because I was very excited to use Moya.

Routers in other Languages

Using my experience in backend development, I decided to revisit some old Ruby-on-Rails projects to get some ideas on how a Router should look. If you open a Rails project inside of a terminal, and run the command rake routes, the following will be printed:

As you can see, this is doing a great job describing the application using very little information. This definitely seems like the way to go. Let’s see how we would build something like this in Swift.

Build Router using Protocol Oriented Programming

So what exactly am I looking for then? What would be cool is if I can declare a route and easily communicate, “Hey, this route can do the following: .get, .post, .delete, but it can’t do .put”.

Anyways, I should declare the route once and not have to repeat all the different HTTP Method mutations, aka, not have a huge switch statement. This will be hard to do using enums. Instead, let’s use structs and protocols! Let’s start designing this. Here’s some pseudo code to help illustrate what I’m shooting for:

There’s still one more thing we need to do to RouterProtocol.swift. We have our protocols, but they aren’t playing well with Alamofire. Since Alamofire.request(_:) is expecting an object that conforms to URLRequestConvertible, let’s create a converter that will allow just that.

Append the following to RouterProtocol.swift :

...protocolRequestConverterProtocol:URLRequestConvertible{varmethod:HTTPMethod{getset}varroute:String{getset}varparameters:Parameters{getset}}/// Converter object that will allow us to play nicely with AlamofirestructRequestConverter:RequestConverterProtocol{varmethod:HTTPMethodvarroute:Stringvarparameters:Parameters=[:]init(method:HTTPMethod,route:String,parameters:Parameters=[:]){self.method=methodself.route=routeself.parameters=parameters}funcasURLRequest()throws->URLRequest{leturl=tryRouter.basePath.asURL()leturlRequest=URLRequest(url:url.appendingPathComponent(route))returntryURLEncoding.default.encode(urlRequest,with:parameters)}}

I created a gist with the complete version of RouterProtocol.swift that contains all the code along with comments.

Awesome! now let’s get started on building our router! There won’t be any comments since that was the point of our protocols, to make our code more expressive.

Conclusion

Well, there you go! I hope you found this post useful and learned something from it. I definitely had a lot of fun building this and look forward to implementing Routable into already-existing projects. Please share if you liked this post so we can continue pushing out content via our Dev Blog. If you have any questions please reach out to me via twitter @initFabian.