Pragmatic networking in iOS with Rage

Rage

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

Rare mobile apps nowadays don’t have API. So we have to make networking layer every time in every next project.

From mobile development point of view we see how different each backend implemented. It depends so much on chosen server technologies, there are strict and predictable java and .net, there are flexible and different node.js, ruby and python.
In the same time in mobile app we always have same technology stack and we want clear and predictable code which looks similar from project to project.

State of the world

When you coming to iOS development from Android world you surprises by patterns which are popular here.
For networking there are pretty good Apple's URLSession but many project still uses Alamofire which is successor of AFNetworking. There is Moya, which is higher level of abstraction over Alamofire. Moya is good, we used it on some projects, it’s 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 same enumeration. Each parameter of single request is described in its own place so it’s really confusing when you try to understand everything about some 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, Moshi. No one has a question why are they so good. It’s natural we want such things in iOS too. Swift have no annotation processing we used to in Java/Kotlin so it’s not easy to make direct analogs here. Supposedly Swift 4 solves JSON problem. OkHttp proposes nice builder style syntax for network requests specification. Retrofit besides proposes the way to describe list of requests and how to serialize/deserialize data, which http client to use to make requests.

Rage

Rage does something similar. It’s our library to make API specification more readable and clear. We want to make less mistakes and we want high customization capabilities. That’s why we created Rage.

Library called this way because of emotions we had to deal with when implemented APIs in iOS apps before this library.

Basically we can represent network layer as list of request descriptions and information how to make requests in general (i.e. client).
Client is the thing which makes requests. It knows some general information about all requests.
Each request has its own specific parameters.

At this point we have these parameters for client:

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

And this for each specific request:

  • URL
  • ContentType
  • HTTP Method
  • Relative Path
  • Headers
  • Query parameters (key-value, no-value, array)
  • Path parameters
  • Is this request requires authorization
  • And separate helper methods for some special cases we use a lot:
    • Requests with body (create body from Data, String, any Codable object)
    • Multipart requests (creating multipart requests never been easier)
    • FormUrlEncoded requests (create body with list of key/value parameters)

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

In comparison with Moya flow in Rage is absolutely straightforward. Each request is caged in its own function where all parameters are described.
One of key features for us is surely RxSwift support which makes Rage usage even more profitable.

That's how it looks in 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()
    }

}

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.

Complex case doesn't differ much.
Want to add query parameter, path parameter, json body from Codable object to one request? No problem. Just three more function calls in this request chain.
Want to use Rage just to create request for later use? No problem, .rawRequest() function is for you.
Rage doesn't force developer to use any architecture pattern, basically it's just a handy way to describe requests.

Library is built on top of Apple's URLSession with use of Result microlibrary to encapsulate Content/Error logic for usecase without RxSwift.

Check it out

Everyone who want to check out Rage can do it. Install it via CocoaPods or use code from Github.
But please, don’t use it in production before version 1.0.0. Now it’s not stable enough and its API may change from version to version without backward compatibility. We use it on all our projects and we become smarter, understand some new features necessity, imperfection of some old solutions and update library based on our needs. It's already 2 years old and now we're really close to stable release. Since it's initial version we've successfully used Rage in every our app and it's a pleasure to implement API in iOS.
There is also documentation available which contains all basic information about library features. Also there is Example project where you can see Rage in action.