Better? Enums are your best friend on this

Protocol-Oriented Network in swift

Just imagine hundreds of lines written for every call your app has to make, so you’ll just repeat it, name it after your call: loginUser, getUserData, logoutUser, getUserFriends, etc.

Network layer as a static function in a 1000 line class!

All of this can be and will be put into a single function that’s exactly 4 lines! in this blog and coming ones I’ll be talking about how to get to this result:

UserRequest.login(name:nameTF.text!, pass: passTF.text!).sendRequest{ response in ... }

It has been a while since I started working with the network in my apps, basically, on a daily bases, you call an endpoint to send or receive a request.
I’ve been through many iterations in the network starting with the simple call for our needed service in every view controller with full code rewritten: server URL + path, params, headers, and more! all of it was written in a function repeated through every ViewController, that’s MVC, right? then you improve that using static functions in a single file, or maybe use a singleton, and so on.

So here, I’m trying to give that pile of code another look, using our friends: Protocols & Enums.

Meet crusty, from Apple WWDC presentation

Many of the network layer approaches I’ve seen also came from Objective-C and made it to swift and that’s what Crusty indeed told you, this’s the way we used to do it and so should you! Guess what? No, you don’t, swift gives you the power to do better! Why not do it better then?!
So, Let’s build that layer!


URLRequest:

Url request is the main element in the network layer, it’s the object you always assemble to send it through your layer and the web in order to get it data to and from your server, and that’s what you actually need to recreate in every server call place through your app.

So you don’t need to repeat the network code, do a 1000 line class that’s called”APIManager, ServerManager, APIShared, APIServer” and make it a singleton without having the need to. (Yes I’ve seen this for real)! You simply just need to reconstruct the request depending on your needs aka path, parameters, headers, body content, etc.

What you also get from this approach is not having to write your parameters keys in every place you wanna call network, which is very error prone in my opinion and it looks very ugly!

This approach is already has been made by a network layer library, meet: Moya:

You’re a smart developer. You probably use Alamofire to abstract away access to URLSession and all those nasty details you don't really care about. But then, like lots of smart developers, you write ad hoc network abstraction layers. They are probably called "APIManager" or "NetworkModel", and they always end in tears.

What Moya doing is actually the same I’m doing here, you basically build a request with it through its builders, which is using Enums and then sending the request through it to alamofire, so if you want to take my implementation to the next level you should definitely use Moya.

Using Enums, which I’m using it more recently to handle different tasks. So in case you wanna learn more about enums I always refer to this blog post about advanced and practical enum usages:

Separation of concerns in creating a request object gives us the chance to test the request: params, paths, or tokens in the header, Unit tests will just love you!

So how to create it? Meet the “Router” way:

Alamofire comes with the idea of creating the router, check this tutorial:

To create your request you now simply do it with an enum confirming to URLConvertible, which is a simple protocol that requires having a function that created a URL request

func asURLRequest() throws -> URLRequest { }

and your enum becomes:

enum TodoRouter: URLRequestConvertible {
static let baseURLString = "https://jsonplaceholder.typicode.com/"

case get(Int)
case create([String: Any])
case delete(Int)

func asURLRequest() throws -> URLRequest {
var method: HTTPMethod {
switch self {
case .get:
return .get
case .create:
return .post
case .delete:
return .delete
}
}

let params: ([String: Any]?) = {
switch self {
case .get, .delete:
return nil
case .create(let newTodo):
return (newTodo)
}
}()

let url: URL = {
// build up and return the URL for each endpoint
let relativePath: String?
switch self {
case .get(let number):
relativePath = "todos/\(number)"
case .create:
relativePath = "todos"
case .delete(let number):
relativePath = "todos/\(number)"
}

var url = URL(string: TodoRouter.baseURLString)!
if let relativePath = relativePath {
url = url.appendingPathComponent(relativePath)
}
return url
}()

var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = method.rawValue

let encoding: ParameterEncoding = {
switch method {
case .get:
return URLEncoding.default
default:
return JSONEncoding.default
}
}()
return try encoding.encode(urlRequest, with: params)
}
}

This’s a way to do it, but if you’re creating many enums like above for your app different parts, something to match your postman collection or api docs for example:

Postman API collection

So you’d have User, Product, Order, Payment, Information enums in your app matching the above collection, let’s see how to do this!

We will be using many configurations in our requests that will be matching in every enum, so let’s get them all in a single protocol that rule them all so every enum can write way less code to be created, just paths and params mostly!

URLRequestBuilder:

We simply add headers, main url, and the required elements to create our request, we also add URLRequestConvertible to the enum conformance in order to do it here once instead of rewriting it in an every enum as well!

None Alamofire users:

Notice: URLRequestConvertible is a simple protocol from Alamofire that you can replace it with your own, simply copy it, or just create a protocol that has one function:

func asURLRequest() throws -> URLRequest

After that you just create a new enum, and add the previous protocol to it and you suddenly have requests ready with every case you add to it!

let’s see UserRequests enum:

The request has the elements that actually changes, the params, the path, and that’s it for most of apis!

ServerPath is an enum with strings for the server paths, it looks like this:

Serverpaths enum

Do you think that was hard? It’s very simple, testable, and easy to create!

To make for example a login request, you’ll just need to write a single line! and there it’s, now you’ve a request to be sent through the network layer!

How to send it?

let request = UserRequest.login(email: “sent email”, password: “password”)
Alamofire.request(request).response ...

So far we created the request, sent it, and all done! 
But I’ve another thing, that’s depending on protocols too, that will make you not have to add “import Alamofire” in your network caller class!
Another horror movie is indeed handling the response, which will be a good place to work on too in coming posts.

check the 2nd part:

all the code is on the github repo too:

Cheers and comments are appreciated🍻, also I’m on twitter @yoloabdo if you wanna talk or discuss anything.

Thanks! and happy coding times!


More where this came from

This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more product & design stories featured by the Journal team.