Networking layer in Swift 5
Building a clean and modularized networking layer using generics and Result types
Networking layers can get very messy especially when working with reading or updating from an API or external source. This may involve in sending over many requests which result in writing many functions that have pretty similar code. ☝️ But! I’m here to save you all from creating many repetitive functions — with the use of generics.
Generic code enables you to write flexible, reusable functions that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.
source: The Swift Programming Language
Let’s get into the part where we start building a clean networking layer from the ground up. This involves exactly 2 parts:
1. Router: Where we build up all our routes.
2. Service Layer: Where we build up our network request.
For sample purposes, I will be using Shopify’s routes and calling to their API with the following 3 routes.
all sources: all information of the collections.
all product ID's: product id of each product in a collection.
all products info: product info for each product in the collection.
Building up a URL
If you’ve ever sent url requests to servers in Swift, you can either use the URL string instantiation and put the entire route in, or you can use URLComponents to build up the url piece by piece. Since we are using many routes, we will use URLComponents to build up the url. Below is an example of what URLComponents consist of.
Although we can create a url from it’s string initializers, it can however get really messy when appending components to the path. Especially when dealing with the query parameters. In the case where our url requires different components each time(e.g. passing in different product id’s to grab the information of each product), using the URLComponents structure would be most suitable. Below you’ll see how we structure the enum to build out the components. More on URLComponents here.
Let’s dive into the Router where we will build up the 3 routes.
1. Create an enum — Router — in which the cases will represent the requests endpoint you want to send to the API. In this case there will be 3 cases since we have 3 requests each with different url.
2. Create the scheme variable which is the same for all cases.
3. Create the host variable which is the same for all cases.
4. Create a path variable which returns the path for each case.
5. Create an array of URLQueryItem’s to build up the parameters since all routes have more than one parameter.
6. Back to the url components image, there are exactly 2 parameters and each are separated by the ampersand, “&”. A URLQueryItem represents one parameter. Each instantiation requiring a name, representing the parameter name to the left of the equal sign, and the value, representing the string on the right of the equal sign. Therefore, we create an array of URLQueryItems for the parameters in each route. And since we’re passing in the access token every time for the value, we can just create a variable that stores that value on line 34.
7. Create a method variable which indicates what method we are using to request to the server. This wouldn’t be necessary if all cases(requests) are a ‘GET’ method since url requests are ‘GET’ by default.
Find the full code for the Router here.
Our service layer will consist of one function that sets up the entire request. And this is where generics come in. In this case, the generic represents the model object we are decoding from the request. Therefore, the generic(T) must conform to the Codable protocol. (a type that can decode or encode itself from or to an external representation. src: apple documentation)
Let’s check it out step by step.
- We use ‘class func’ so we can call the function directly on the class without having to instantiate it. And we go ahead and add the generic.
• We pass in 1 parameter which will be of type Router.
• We create an escaping completion handler to pass the decoded data, or an error object to use elsewhere even after the function has returned.
• Result type to the rescue! (available in Swift 5) We declare the first parameter to be the object type and the second to be the Error type.
- Set up the components of the url from our handy Router class.
- Make sure the url is valid and then create the url request with the correct method.
- Start the request to the server with the urlRequest we created above. Don’t forget the .resume() after the function to start the task since newly-initialized tasks begin in a suspended state. (src: apple documentation)
- Use a guard statement to make sure there are no errors or else pass the error into the completion and return out of the function.
- By this step, we are guaranteed there is data, response, and no error. Start decoding the json object from our model which will be defined later when we call the function.
- Dispatch back to the main thread before calling the completion handler since networking happens in the background thread. Or, you can either choose to dispatch back to the main thread when you use the response object when calling the function.
- Pass the decoded object in the completion handler in order to use it elsewhere even when the function returns. Completion handlers are mainly used to trigger an action by the object inside of the function. Which in this case, you will see how we use the object when we call the function later on.
We’ve completed setting up the networking layer! Feel free to use this template to fit your needs. Add and modify any cases in the Router or the type in the Service Layer as you prefer.
Below, I will provide an example on how to call the network function as well as providing some starter code for the models. (Which I’m assuming you know how to create models for decoding json data: check this article for more)
Now we can call our network request since we have models to decode from. If you may have guessed, these models will be the ‘T’ when we declared our generics. So, let’s take a look how we actually tell the service layer what the ‘T’ is.
This function is called wherever you want to fire up the request(in the viewDidLoad, IBAction, etc.). As you may notice, we call the request function directly on the ServiceLayer class, this is because of how we used the keyword ‘class func’ in the Service Layer class which allows us to use it directly on the class without having to instantiate it. We pass in the Router case which will handle in building the route and we specify the result type in the completion handler. Once this function is fired, the request function knows what route to build and what model to decode from. And once we receive the Result object, it will be in either a success or failure case which you then handle what to do in both cases.
I hope this made you appreciate generics as much as I do! 😄
Drop some claps 👏👏👏 if you found this helpful 💡
All and any feedback is accepted. 📩