REST Client in Swift with Promises

Diving deeper into Swift, I am examining different ways of improving my architecture, and making the best decisions when creating new applications. For me, architecture and expressiveness  in the code is much more important that a complicated algorithm that boosts performance over 100%.

One thing I am called to do very often, is set up an HTTP client that will handle network operations, access tokens, and perform bandwidth throttling. So, I would like to share with you my idea of approaching the creation of an HTTP client with Swift.

We will use the following frameworks:

  • Alamofire for HTTP Calls.
  • ObjectMapper for mapping HTTP Responses to objects
  • PromiseKit for making your code more… promising! I am a very strong applicant of promises, both in Javascript and iOS, and I strongly recommend it for making asynchronous code clearer and more scalable, without compromising flexibility.

Lets suppose you have this kind of API:

Let’s suppose that all API responses have this format where:

  • Error holds an explanation of the error that happened in the server (missing arguments, invalid tokens, etc). Let’s imagine that error is an object holds two values: “errorMessage” and “errorCode”, both strings that are returned from the server.
  • Response holds an object indicating the response depending on the request (“for example, the response can be an array of objects, indicating a product list, or the user’s profile details)
  • Success is a boolean indicating that the operation went well. If success is “false” the “error” will hold an explanation of what went wrong.

For this example, I opted to use the root object containing ‘success’, ‘error’ and ‘response’, since I usually like to distinguish between logical errors, and server errors. For example, trying to login with invalid credentials is a logical error, and would return ‘false’ in the ‘success’ field, with an error object in the ‘error’ field. And an invalid kind of argument given, or any server error, would be an error 500, or something similar. Everything else, is a success, accompanied by a ‘response’ object.

Simple HTTP client implementation (code)

We will use a simple Swift Enum as the router for our application. By having an enum, we can have autocompletion for our server routes, and avoid redundant duplicate strings when trying to access a server resource / path.

Before we start implementing our HTTP Client, we should make the most basic parseable object. Here’s what we know so far, that will play a role into our model design:

  • The base response is the same. There is always an “error”, “response” and “success” property in the response.
  • Those base properties are only used to determine if there is an error during the request to the server. If the request is successful, we don’t need them anymore.
  • If the request is successful, what we care about, is the object contained inside the “response” object.
  • If the request fails, we show the error, and we don’t call the “success” callback, in case we had any.

Based on these assumptions, let’s begin modelling our response objects using ObjectMapper’s functionality.

We model the basic response as a generic, since the “response” object can be any type of object. Node that the type of T is defined to be a Mappable object. This is by design. It is our responsibility to provide mappable objects to the “response” objects in order for it to be parsed.

We also define an error class. Although I tend to refrain myself from subclassing NSError, in this case, I opted to do it, in order to help me explain better this use case.

Based on these models, we proceed with the implementation of the HTTP Client

How it works

The general concept is that when downloading the data, it is parsed and made into an object, which is then returned inside a Promise. The “magic” happens when determining the class and kind of parsing for the object to be returned.

This is done through specialising the T parameter when calling HTTPClient’s ‘get()’ or ‘post()’. By specializing it, the mapper knows which mapping class to call when downloading the data.

Consider the following JSON:

If we want to map it, we can map it like this, with ObjectMapper;

We need to only map the “response” content. Remember that the outer element, the one which contains “success” and “error” is already parsed. It will be used to determine if an error has happened when the JSON arrives, but after that step, we only need the content of the ‘response’ object to be returned to us.

We can now use our HTTPClient, and parse our first response.

Conclusion

You can now chain HTTP requests, automatically parse the responses, and have a nice and clean HTTP client as the basis of all your requests. I suppose that this methodology can work for SOAP Requests, too, with a few modifications.

Depending on the structure of your API, you will probably have to change your approach with parsing.

  • Euthimis Liapatis

    I find this approach really interesting. Makes your life easier with calls. However, I would use a different approach in the way of Errors are being handled. Since promise .error{} returns an ErrorType it would be more convenient and more swifty to use a protocol for the abstract implementation of an error and an enum that conforms the previous protocol and ErrorType protocol. To sum up, I would would replace class APIErrorResult with the code below:

    protocol CustomError {
    func userInfo() -> Dictionary?
    func errorDomain() -> String
    func errorCode() -> Int
    }

    extension CustomError{
    func error() -> NSError {
    return NSError(domain: self.errorDomain(), code: self.errorCode(), userInfo: self.userInfo())
    }
    }

    enum APIErrorType: ErrorType, CustomError {
    case NetworkError
    case GenericError

    case SpecificError(errorFromAPI : APIError?)

    func errorDomain() -> String {

    return “CustomDomainError”

    }

    func errorCode() -> Int {

    switch self {

    case .NetworkError:

    return -100

    case .GenericError:

    return -101

    case.SpecificError(errorFromAPI: let apiError):

    guard let apierr = apiError, let _ = apierr.errorName else { return -101 }

    return -102

    }

    }

    func userInfo() -> Dictionary? {

    return [“apiError” : self.errorDescription]

    }

    var errorDescription: String{

    switch self {

    case .NetworkError:

    return “Network Error”

    case .GenericError:

    return “Generic Error”

    case.SpecificError(errorFromAPI: let apiError):

    guard let apierr = apiError, let errName = apierr.errorName else { return “Generic error” }

    return errName

    }

    }

    }

  • frontier

    Hello, I tried an example with this code but I have a problem. If response is an array how can I map it?