FunNet: A fully modular Network Layer

At LithoByte, most of my work is spent developing tools that are widely applicable and used across most, if not all, apps. The time spent on these tools pay their dividends when we use them in client work, or in the development of other tools. One thing that most apps will have is some type of network layer that moderates the communication between front end and backend, as well as handling errors that may occur in a diagnosable way. A couple of years ago, when I was coding both the front end and backend of a passion project, I didn’t see much use in a generalizable network layer — I would just store the server URL in the environment, and append whatever URL path string I needed, and then take the raw JSON as a [String: Any?] and use that to display what I needed to on the front end. It was a pain, but I didn’t know any other way to do it.

Working for LithoByte, however, I’ve been very comfortable with a library called FunNet (https://github.com/LithoByte/funnet) which makes networking so much easier, as I’ll explain to you shortly. FunNet is usable across all iOS versions post-10.0, and post-13.0 uses the Combine Framework to achieve the asynchronous nature of networking.

Whichever iOS version you are working with, however, Elliot Schrock has done a great job of starting with a strong foundation and building off of it. Thinking about a network layer, what changes and what doesn’t? The URL will change, but the base URL string will mostly stay the same. You will probably need to do different things to the response, the data, and the optional error depending on the data you expect to come back, but the fact that you need to tap into those types are constant.

Without further ado, let’s take a look at the first class that encapsulates everything about the actual URL string except for the path: ServerConfiguration:

open class ServerConfiguration: ServerConfigurationProtocol {
    public let shouldStub: Bool
    public let scheme: String
    public let host: String
    public let apiBaseRoute: String?
    public var urlConfiguration: URLSessionConfiguration

    public init(shouldStub: Bool = false, scheme: String = "https", host: String, apiRoute: String?, urlConfiguration: URLSessionConfiguration = URLSessionConfiguration.default) {
        self.shouldStub = shouldStub
        self.scheme = scheme
        self.host = host
        self.apiBaseRoute = apiRoute
        self.urlConfiguration = urlConfiguration
    }
}

As you can see, it really serves only as a wrapper for the different components of a URL scheme, and providing smart defaults so the only information you really need to supply are the host and an optional apiRoute (e.g. ‘api/v1’). Now for the counterpart of the ServerConfiguration, called the Endpoint:

public struct Endpoint: EndpointProtocol {
    public var httpMethod: String = "GET"
    public var httpHeaders: [String: String] = [:]
    public var path: String = ""
    public var getParams: [String: Any] = [:]
    public var timeout: TimeInterval = 60
    public var postData: Data?
    public var dataStream: InputStream?
    
    public init() {}
}

Here we have all the things that can or will change about the actual URL: method, path, parameters, postData (analogous to request body), etc. It shouldn’t be difficult to see that Endpoint and Server Configuration, when paired together, have all the information necessary to fire off any JSON-encoded URLSession. These two classes serve as the base for our networking layer, but we still need to abstract one more layer to enable the user to “tap in” to anything we might get in a URLSessionDataTask callback. We need a NetworkResponder to do this:

public protocol NetworkResponderProtocol {
    var taskHandler: (URLSessionDataTask?) -> Void { get set }
    var responseHandler: (URLResponse?) -> Void { get set }
    var httpResponseHandler: (HTTPURLResponse) -> Void { get set }
    var dataHandler: (Data?) -> Void { get set }
    var errorHandler: (NSError) -> Void { get set }
    var serverErrorHandler: (NSError) -> Void { get set }
    var errorDataHandler: (Data?) -> Void { get set }
}

All these methods, when implemented, will be run in the callback of a URLSessionDataTask. With all of these in place, we can define our wrapper protocol that holds all of these, and from which you can produce a URLSessionDataTask:

public protocol NetworkCall: class {
    associatedtype ResponderType: NetworkResponderProtocol
    
    var configuration: ServerConfigurationProtocol { get set }
    var endpoint: EndpointProtocol { get set }
    var responder: ResponderType? { get set }
}

It should be fairly simple to see how a dataTask can be constructed:

  • Generate a URL from the configuration and endpoint
  • Generate a URLRequest from that URL
  • Generate a dataTask from that request in the callback of which you call the responder‘s required functions on the parameters of the callback.

2 thoughts on “FunNet: A fully modular Network Layer

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: