Pragmatic networking in iOS with Rage

Disclamer: As for 2020 the library is not supported by me and it's poorly supported to use with newer Swift versions. But I like the idea of the library, which is still interesting.

This post is about Rage, which is our library to make abstraction over API implementation in iOS.

Few mobile apps these days don’t have an API. Therefore, we have to create networking layer every time, for every new project.

From a mobile development point of view we see how differently each backend is implemented. It depends to a great extent on chosen server technologies, such as strict and predictable java and .net, flexible and different node.js, ruby and python. At the same time, when dealing with mobile apps, we always have the same technology stack and we want to have a clear and predictable code, which looks similar for every project.

State of the world

When you transition to the realm of iOS development from the Android world you will inevitably be surprised by patterns that are quite popular here. For networking purposes, there is pretty good Apple’s URLSession, but many projects still utilize Alamofire, which is a successor of AFNetworking. There is Moya, which is a higher level of abstraction over Alamofire. Moya is decent, and we used it in some projects. It is well documented and tested, but its enum-abusing syntax is something we couldn’t live with. API description instantly turns into tons of switch-cases on the same enumeration. Each parameter of a single request is described in its own place, and it is really confusing when you try to understand everything about a single request. We accept Moya’s ideology clearance but it affects our productivity. Sometimes it looks like Moya painted itself into a corner.

Key buzzwords of Android world are OkHttp, Retrofit and Moshi. No one questions as to why they are so good. It is only natural that we want such things in iOS as well. Swift have no annotation processing we are used to in Java/Kotlin, and it is therefore not easy to make direct analogs here. Supposedly, Swift 4 solves the JSON problem. OkHttp proposes a nice builder style syntax for the network requests specification. Retrofit proposes the way to describe a list of requests, how to serialize/deserialize data and which http client should be used to make requests.

Rage

Rage does something similar. It is our library to make API specification more readable and clearer. We strive to make fewer mistakes and we aim to have high customization capabilities. That is why we created Rage.

We gave the library this name because of all the emotions we had to deal with while implementing APIs in iOS apps before this library.

Basically, we can represent a network layer as a list of request descriptions and information on how to make requests in general (i.e. client). A client what makes requests. It is aware of general information about all requests. Each request has its own specific parameters.

At this point, we have following parameters for the client:

  • Base URL
  • Base ContentType
  • URLSessionConfiguration
  • Some plugins, e.g. logger
  • Headers for all requests
  • Request Authorization process description

And this is what we have for each specific request:

  • URL
  • ContentType
  • HTTP Method
  • Relative Path
  • Headers
  • Query parameters (key-value, no-value, array)
  • Path parameters
  • If this request requires authorization

We often use helper methods for special cases:

  • Requests with body (create body from Data, String, any Codable object)
  • Multipart requests (creating multipart requests never been easier)
  • FormUrlEncoded requests (create a body with a list of key/value parameters)

Serialization and deserialization can also be integrated into the request making process. Our goal is to work with strictly typed objects, not the dictionary hell we see in many projects. To be honest we hate these [String: Any?] things. Now in Swift 4, we have Codable so we can just pass Codable object in the request body and it will be serialized to json. We can specify request Codable return type and get the deserialized object from json.

In comparison to Moya, the flow in Rage is very straightforward. Each request is caged in its own function where all parameters are described. One of our key features is surely RxSwift support, which makes Rage usage even more profitable.

That’s how it looks in a simple case

class GithubAPI {
 
    let client: RageClient
    
    init() {
        client = Rage.builderWithBaseUrl("https://api.github.com") // Creating client builder
        .withContentType(.json) // Set all requests ContentType to application/json
        .withPlugin(LoggingPlugin(logLevel: .full)) // Enable logging
        .build()
    }
    
    func getOrgRepositories(org: String) -> Observable<[GithubRepository]> {
        return client.get("/orgs/{org}/repos") // Setup request http method and path
        .request() // Create simple request with given configuration
        .path("org", org) // Add parameters to created request
        .executeObjectObservable() // Execute request, deserialize json response to GithubRepository array and return it as Observable
    }
    
    func getContributors(repo: String, org: String) -> Observable<[GithubUser]> {
        return client.get("/repos/{owner}/{repo}/contributors")
        .request()
        .path("owner", org)
        .path("repo", repo)
        .executeObjectObservable()
    }
    
    func getOrgInfo(org: String) -> Observable<GithubOrganization> {
        return client.get("/orgs/{org}")
        .request()
        .path("org", org)
        .executeObjectObservable()
    }

}

As you can see, this looks very simple for use in external code. It’s just a function getOrgInfo("gspd-mobi"), which is called in your business logic like any other function in your code.

A complex case doesn’t look much different. Do you want to add a query parameter, path parameter, json body from Codable object to one request? No problem. Just three more function calls in this request chain. Do you want to use Rage just to create a request for later use? No problem, .rawRequest() function is for you.

Rage doesn’t force developers to use any architecture pattern; basically, it’s just a handy way to describe requests.

The library is built on top of Apple’s URLSession with the use of Result microlibrary to encapsulate Content/Error logic for a use case without RxSwift.

Check it out

Everyone can check out what Rage is all about. Install it via CocoaPods or use the code from Github. But please, don’t use it in production before version 1.0.0. It’s not yet stable enough, and its API may change from version to version without backward compatibility. We use it in all our projects and we learn on our mistakes, we grew to understand which new features we need to add, what imperfections some old solutions have and how the library must be updated based on our needs. It is already 2 years old, and we are now really close to releasing stable version. We have successfully used Rage in all of our apps since the release of its initial version and it’s been a pleasure to implement API in iOS. There is also documentation available which contains all basic information about the library features. In addition, there is also an Example project that will allow you to see Rage in action.