Header

Each day more and more people are connected to the world wide web. The more their number grows the demand for their apps to be connected to the internet is also increasing.

At Halcyon Mobile, over the years, with the constant change in technology and frameworks, we’ve been continuously improving our architecture and network abstraction wasn’t an exception.

We’ve been through many iterations and internal frameworks until we found a fitting and abstract solution.

Solution

We call it RestBird, and it’s a lightweight, easily extendable network abstraction layer for REST endpoints which you can use via CocoaPods, Carthage or Swift Package Manager.


Goal

When we decided to open source RestBird we had a couple of goals in mind:

  • Keep it simple, stupid (KISS).
  • Hide network operations inside the abstraction.
  • Rely on the Codable protocol.
  • Avoid any dependency.

Usage

To demonstrate how the framework can be used, we’ll go through an example of how it works. 😄

Let’s take a sign in endpoint where the username and password are sent as the body of a POST request and on successful authentication, the backend returns a Bearer token alongside the user. 

Here’s how the response would look like:

{
    "token": "access token",
    "user": {
        "username": "john.doe",
        "full_name": "John Done",
        "image_url": "http://example.s3.amazonaws.com/avatar.png"
    }
}

To make the request, first we need to define the request object using RestBird’s DataRequest protocol:

struct LoginRequest: DataRequest {
    typealias ResponseType = AuthResponse
    let username: String
    let password: String

    var suffix: String? { return "/sign-in" }
    var parameters: [String : Any]? {
        return ["username": username,
                "password": password]
    }
}

By conforming to DataRequest, we can describe the request by its attributes: ResponseType (associated type), HTTPMethod, suffix, parameters, headers.

While the last 4 attributes might be straightforward for any URL requests, ResponseType is used for defining our expected response type and providing a centralized parsing.

Now let’s head over to create the response object. In our case is two nested objects (User and AuthResponse).

struct User: Decodable {
    let username: String
    let fullName: String
    let imageUrl: URL
}

struct AuthResponse: Decodable {
    let token: String
    let user: User
}

To make the network request we need to configure the NetworkClient with a Session Configuration as follows:

struct APIConfiguration: NetworkClientConfiguration {
    let baseUrl = "https://api.example.com"
    let sessionManager = AlamofireSessionManager()
    let jsonDecoder = JSONDecoder()
    let jsonEncoder = JSONEncoder()
}

NetworkClientConfiguration also gives you a way to define how requests and responses are serialized through its jsonDecoder and jsonEncoder attributes. This could be useful for configuring date and data decoding strategies or converting snake case JSONs automatically.

Now we can initialize our client and make requests. 🎉

let client = NetworkClient(configuration: APIConfiguration())

let request = LoginRequest(username: "test", password: "supersecret")
client.execute(request) { result in
  switch result {
  case .success(let auth):
    // yay! :D
  case .failure(let error):
    // nay! :(
  }
}

RestBird comes with two built-in drivers available in different packages: one uses Alamofire while the other uses URLSession under the hood (here’s how they work).


Dynamism

While keeping your request and response type-safe is a good practice, you might want to manipulate the request before or after sending it. This might be useful for signing your requests with authentication token or logging network errors.

The concept was inspired by other web frameworks like Node.js Express.

struct LoggerMiddleware: PreMiddleware {

    func willPerform(_ request: URLRequest) throws {
        Logger.log(resquest)
    }
}

// Register middleware
networkClient.register(LoggerMiddleware())

Middlewares can intercept the request in two points: PreMiddlewares are evaluated before the request is performed (as the example shows) while PostMiddlewares are performed after a response is returned.


Extensibility

RestBird by itself does not and will not depend on any 3rd party framework to keep the core as clean and independent as possible.

Keeping that in mind, we choose blocks for asynchronous one-to-one communication between the framework and your code.

Depending on your architecture blocks might be a pain to use so we created a set of handy extensions to communicate with the main interface. These extensions include a RxSwift and a PromiseKit extensions.


The future

There might be no fits-all solution, but having the project open-sourced could help the project evolve into something that could be useful not just for us but for everyone facing the problem.


We’d be more than happy to hear your thoughts and suggestions! 😀

For more examples, head over to RestBird’s GitHub page where you can find a more detailed documentation.