Skip to content

hiragram/AbstractionKit

Repository files navigation

AbstractionKit

Build Status

AbstractionKit provides various protocols and structs that make it easier to abstract json APIs.

AbstractionKit does not depend on any other networking and mapping frameworks. So you can bridge between AbstractionKit and any frameworks you usually use.

Overview

Define your API endpoint as follows:

struct GetUser: EndpointDefinition {
    /// Means this endpoint returns single User object.
    typealias Response = SingleResponse<User>

    /// Path for resource.
    static var path: String = "/user"

    /// You can switch server for each endpoint.
    /// (You can use .staging or .mock if you defined.)
    static var environment: Environment = .production

    /// Parameters to contain in request.
    let parameters: [String: Any]

    /// HTTP method
    var method: HTTPMethod = .get

    init(userID: Int) {
        parameters = [
            "id": userID
        ]
    }
}

then simply extract objects from JSON like so:

// `json` is a dictionary that API returned
let user = try GetUser.Response.init(json: json).result

Sample app

A sample that uses APIKit, Himotoki, and RxSwift is available. Clone and run $ carthage update then run AbstractionKitSample.

Installation

Requirements

  • Swift 4.0
  • Xcode 9.x

Carthage

  • Add github "hiragram/AbstractionKit" ~> 0.1 to your Cartfile.
  • Run $ carthage update.
  • Add built framework in Carthage/Build/iOS/ to your project.
  • Append AbstractionKit.framework to arguments of $ carthage copy-frameworks.

How to define response

See: How to define response

How to bridge between AbstractionKit and networking frameworks

Create a wrapper for the network framework. The wrapper transforms AbstractionKit's endpoint instance to network framework's request object and generate object from response JSON.

For example, wrapper for APIKit is as follows,

struct APIKitBridgeRequest<Endpoint: EndpointDefinition>: APIKit.Request {
    typealias Response = Endpoint.Response.Result
    var baseURL: URL = Endpoint.environment.url(forPath: "")

    var path = Endpoint.path

    var parameters: Any? {
        return endpoint.parameters
    }

    var method: APIKit.HTTPMethod {
        return endpoint.method.apiKitMethod
    }

    private let endpoint: Endpoint

    init(endpoint: Endpoint) {
        self.endpoint = endpoint
    }

    func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Endpoint.Response.Result {
        guard let jsonObj = object as? Endpoint.Response.JSON else {
            fatalError()
        }
        return try Endpoint.Response.init(json: jsonObj).result
    }
}

This wrapper has AbstractionKit's endpoint definition as a generic type parameter, and conforms to APIKit.Request protocol.

Then you create an instance of APIKitBridgeRequest and execute request.

// `GetUser.Response` is `SingleResponse<User>`
let endpoint = GetUser.init(userID: 100)
let request = APIKitBridgeRequest.init(endpoint: endpoint)
Session.send(request, callbackQueue: nil, handler: { (result) in
    switch result {
    case .success(let user):
        print(user)
    case .failure(let error):
        print(error)
    }
})

How to bridge between AbstractionKit and mapping frameworks

SingleResponseElement and ArrayResponseElement are available to define model object types. Each protocol constraints to implement decode method. You can use any mapping framework in the method as follows,

struct User: SingleResponseElement, Himotoki.Decodable {
    static var singleKey = "user"

    var id: Int
    var name: String

    /// Implementation for Himotoki.Decodable
    static func decode(_ e: Extractor) throws -> User {
        return try User.init(
            id: e <| "id",
            name: e <| "name"
        )
    }

    /// Implementation for AbstractionKit.SingleResponseElement
    static func decode(from json: Any) throws -> User {
        // Using Himotoki internally.
        return try decodeValue(json)
    }
}

AbstractionKit does not depend on any mapping framework, so you can also map JSON to object manually. (Of course I will never recommend it.)

struct User: SingleResponseElement {
    static var singleKey = "user"

    var id: Int
    var name: String

    /// Implementation for AbstractionKit.SingleResponseElement
    static func decode(from obj: Any) throws -> User {
        let json = obj as! [String: Any]

        return User.init(
            id: json["id"] as! Int,
            name: json["name"] as! String
        )
    }
}

How to create cool abstraction layer using AbstractionKit

work in progress

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published