By

Moya, Decodable, and RxSwift Bindings

If your application integrates with a REST API, one of the first things you’ll implement is a network abstraction and serialization layer. Moya and Decodable are examples of libraries that make this dead simple for Swift / iOS. You don’t want to repeat the same boilerplate code accessing each resource, so let’s create generic extensions that will make our life easier.

Moya and Decodable

Moya is a thin facade around the Alamofire HTTP client. Instead of writing Alamofire.request("https://api.mysite.com/v1/orders/\(userId)") and managing parameters, paths, encoding, etc., we can implement Moya’s protocol and access an endpoint via enum: MoyaProvider<MyApi>().request(.getOrders(userId)). Not only does this keep our API configuration encapsulated and consistent, but it’s much easier to read. The API call returns a Moya.Response, which ultimately needs to be converted into a model.

Decodable is an object mapping library that handles JSON serialization and deserialization into Swift objects. Mappings are specified as extensions on models and–once specified–deserialization is a simple call to the decode extension: let orders = try [Order].decode(json).

Even in the simplest case of making a request and mapping a response, we want to avoid writing the above code for every single resource we interact with. Here’s a generic extension that will handle mapping for us against any response; all we need to provide is the type information:

public extension Moya.Response {
    public func map<T: Decodable>(_ type: T.Type) 
      throws -> T {
      return try type.decode(try self.mapJSON())
    }
    
    public func map<T: Decodable>(_ type: [T.Type]) 
      throws -> [T] {
      return try Array<T>.decode(try self.mapJSON())
    }
}
  • Here we see two map methods, the first for returning a single mapped object and second for an array of objects.
  • The self.mapJSON() call is just a Moya extension that will convert a Response to JSON using JSONSerialization.jsonObject. Feel free to use your own serialization code.

The net result is the API calls are now fluent, succinct, and readable for all resources:

internal func getOrders(userId: String) throws -> [Orders] {
      return provider.request(.getOrders(userId))
                     .map([Orders.self])
}

RxSwift-ifying our Code

If you use RxSwift and want to utilize the above extensions in an Observable chain, we can employ the same strategy with the flatMap and just operators:

public extension ObservableType where E == Moya.Response {
  public func map<T: Decodable>(_ type: T.Type) 
  -> Observable<T> {
    return flatMap { (response : Response) 
    -> Observable<T> in
      return Observable.just(try response.map(type))
    }
  }

  public func map<T: Decodable>(_ type: [T.Type]) 
  -> Observable<[T]> {
    return flatMap { (response : Response) 
      -> Observable<[T]> in
        return Observable.just(try response.map(type))
    }
  }
}

The reactive code is now just as clean and easy to read as the non-reactive code:

provider.rx.request(.getOrders(userId))
        .map([Orders.self])
        .subscribe { orders in
          // Handle orders ...
         }

Improvements

As an exercise, I suggest you consider these improvements for a “real” implementations:

  1. Utilize ViewModels. Your API call(s) may return data that does not exactly conform to your view. Are there any well established design patterns that can help you handle this separation of concerns? How would this code work with the above examples?
  2. Errors in an RxSwift observable chain will terminate a subscription, which is something you want to avoid in most cases. If any sort of error occurs in the above examples (network issue, mapping issue, etc.), our subscription will be terminated and updates will cease. How might you handle this problem?

All of the above code is available on GitHub here.