Write your own Network layer in Swift

Swapnil Sankla
DevData
Published in
5 min readJul 10, 2018
Image source

Let us try to build our own Network layer in pure Swift. It is super simple to build with Swift’s strong support for generics, type inference

So let’s start with basics. This network layer is based on URLSession object. By definition this is an object that coordinates a group of related network data transfer tasks. You don’t even need to create a custom object of URLSession in most of the cases. We will also keep it simple and use the default shared instance.

Get Requests

To make a GET request let’s use a dataTask method on URLSession. The method looks like below.

open func dataTask(with request: URLRequest, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void) -> URLSessionDataTask

We need to pass following parameters:

  1. request: GET requests are pretty simple. We just need endpoint and header details. We can create the URLRequest with these.

2. completionHandler: It is called as a result of request execution. It contains following fields

  • data: Data? : Contains the response in Data form. As the signature suggests, it is optional. When request succeeds you get data else it will be nil.
  • urlResponse: URLResponse? : Contains auxiliary details like
  1. mimeType
  2. encoding
  3. Headers
  4. Status code etc.
  • error: Error? : Contains error information. On success you receive it as nil.

To determine whether the request has actually succeeded or not, we rely on each of these.

With above details I wrote my first network layer code.

First attempt:

Bingo! It’s super simple. Make sure you call resume() method so that the suspended task gets picked up for execution.

Though the above code is simple however the caller needs to do a lot of things. It needs to take care of building and processing urlRequest and completionHandler. Most of the code which caller writes is actually common. So let’s try to simplify that.

Second attempt:

What did I change?

  1. Instead of completionHandler now the get method accepts success and error handlers.
  2. Success handler is called when the response contains success status code and response is convertible into Data. This makes caller code very easy. Either success or error handler gets called. Caller just need to do the processing accordingly.

Taking a closer look, I felt converting Data to appropriate response object task is also common among all consumers. So why should everyone repeat the logic? That brought me to the third attempt.

Third attempt:

What did I change?

  1. SuccessHandler signature is changed to (T) -> Void. Instead of sending Data let’s send the concrete object itself.
  2. T is restricted to be Decodable. This is the secret recipe. With Codables, we don’t need to write our own parsers to convert data to the appropriate type.

So now how does client code looks like? It’s as below.

Looking closely at caller’s code one can understand the power of generics and type inference. I don’t need to specify what network layer should return on success or on failure.

Still the creation of URLRequest can be extracted out. Let’s do that too!

Fourth attempt:

What did I change?

  1. Consumer just needs to send the endpoint and optionally the headers. Network layer takes care of building URLRequest and handling URL creation failure.

Error Handling

Get method calls errorHandler in one of the following cases.

  1. URL creation failure
  2. Response parsing failure
  3. Status code based error handling
  4. Network layer errors e.g. request timeout etc.

POST Requests

POST requests are equally simple. To make a POST request let’s use a uploadTask method on URLSession. The method looks like below.

func uploadTask(with request: URLRequest, from bodyData: Data?, completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void) -> URLSessionUploadTask

We need to pass following parameters:

  1. request: URL request object.
  2. body: Data which needs to be uploaded
  3. completionHandler: We have already used this one in GET request.

As we already know the power of generics, type inference. So based on that the post method looks like below.

Things to consider

  1. T is restricted to encodable and without knowing the real type we can create Data object from it.
  2. Caller does not need to take care of setting up common POST request params. Following are set inside post method.
  • headers
  • timeout
  • httpMethod
  • httpBody

That’s it! The Network layer now supports GET and POST requests. Although it is not very powerful however it will suffice the need.

Unit testing

I have always seen problems while testing the code which calls network layer. So let’s check whether the caller code is testable or not.

We all know that Swift recommends to not to use mocks and rather rely on the other types of test doubles. We will use Dummy object to test caller method.

Let’s assume you want to test getExample() method from the code below.

Things to consider:

  1. networkLayer is injected into Presenter. This makes it easy for us to inject the dummy object.
  2. The default value of networkLayer is the real NetworkLayer. Hence the caller does not need to create and pass it.

Let’s try to write a test for getExample() method. The focus is to verify whether displayEmployees() is being called or not. We don’t want our test to make real network calls right? So time for dummy objects!

Things to consider:

  1. Inject the dummy network layer while creating presenter.
  2. Inject the dummy view so that we can easily test the view code and assert upon.
  3. If we want to test the success case then set networkLayer.successResponse. Otherwise set networkLayer.errorResponse.

Cool! The code is easily testable, isn’t it? Similarly we can write test method for failure cases and also for the POST request.

You can find the complete code on github

I have published Swift_SimpleNetworkLibrary on cocoapods. The framework also contains the DummyNetworkLayer. Hence the unit testing becomes very easy.

To install Swift_SimpleNetworkLibrary add following into your Podfile.

pod 'Swift_SimpleNetworkLibrary'

Can Rx further simplify our network layer?

Let’s try to use RxSwift to build our network layer. I am not going into the details of the Rx concepts. Let’s check the usage directly. The network layer code looks something like this.

Things to consider:

  1. Returning an observable on which the caller will have to observe upon.
  2. Success and failure handlers are replaced with observer.onNext and observer.onError respectively.
  3. Complete code goes inside a closure which is passed to Observables.create

Now let’s look at the caller code.

We assign onNext and onError just like we set successHandler and errorHandler earlier. Here we get a disposable object from the network layer. It needs to be disposed appropriately.

Caller code remains mostly same in both the approaches. However the get method gets complicated with Rx. Hence after trying both approaches, at least I am convinced that callbacks are simpler than Rx in current use case. Rx are more powerful however here I don’t feel the need right now.

Please clap/share/recommend if you like this article. 😃

Reference: https://swapnilsankla.github.io/Swift_SimpleNetworkLibrary/

This blog is also published on my personal website https://swapnilsankla.me

--

--