Skip to content

Instantly share code, notes, and snippets.

@ishkawa
Last active March 12, 2017 01:26
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ishkawa/f5166f5fbd5a751c3eb7b40fc6b7d1f1 to your computer and use it in GitHub Desktop.
Save ishkawa/f5166f5fbd5a751c3eb7b40fc6b7d1f1 to your computer and use it in GitHub Desktop.

APIKit 4

ETA: Early 2017

Overview

  • Response protocol
  • Refined HTTP body protocol
  • Independent from Objective-C runtime
  • Linux support
protocol Request {
    associatedtype Response: APIKit.Response
    associatedtype HTTPBody: APIKit.HTTPBody

    var method: HTTPMethod { get }
    var baseURL: URL { get }
    var path: String { get }

    var headerFields: [String: String] { get }
    var queryItems: [URLQueryItem]? { get }
    var body: HTTPBody? { get }

    func intercept(urlRequest: URLRequest) throws -> URLRequest
}

Response protocol

Response protocol is introduced to separate responsibility of building request and parsing response.

protocol Response {
    init(data: Data, urlResponse: URLResponse) throws
}

Response type of Request protocol is constrained to conform the Response protocol.

protocol Request {
    associatedtype Response: APIKit.Response
    ...
}

For JSON API, JSONResponse protocol is also introduced.

protocol JSONResponse {
    init(json: Any, urlResponse: URLResponse) throws
    static func readingOptions: [JSONSerialization.ReadingOptions]
}

extension JSONResponse: Response {
    init(data: Data, urlResponse: URLResponse) throws {
        let options = Self.readingOptions
        let json = try JSONSerialization.jsonObject(with: data, options: options)
        init(json: json, urlResponse: urlResponse)
    }
}

This protocol has default implementation of init(data:urlResponse:), which calls init(json:urlResponse:) with the result of JSONSerialization.jsonObject(with:options:). Consequently, required component for conformance is init(json:urlResponse:) only.

For example, suppose we receive {"id": 123, "name": "ishkawa"} as a response. Struct that expresses the JSON response looks like below:

struct User: JSONResponse {
    let id: Int
    let name: String
    
    init(json: Any, urlResponse: URLResponse) throws {
        guard
            let dictionary = json as? [String: Any],
            let id = dictionary["id"] as? Int,
            let name = dictionary["name"] as? String else {
            throw SomeError
        }
        
        self.id = id
        self.name = name
    }
}

Refined HTTP body protocol

protocol HTTPBody {
    func buildEntity() throws -> HTTPBodyEntity
}

struct HTTPBodyEntity {
    enum Content {
        case data(Data)
        case inputStream(InputStream)
    }

    let contentType: String?
    let content: Content
}
struct JSONHTTPBody: HTTPBody {
    let json: Any
    let writingOptions: JSONSerialization.WritingOptions
    let contentType: String?

    init(json: Any, writingOptions: JSONSerialization.WritingOptions = [], contentType: String = "application/json") {
        ...
    }

    func buildEntity() throws -> HTTPBodyEntity {
        let data = try JSONSerialization.data(withJSONObject: json, options: writingOptions)
        return HTTPBodyEntity(contentType: contentType, content: .data(data))
    }
}

extension JSONHTTPBody: ExpressibleByDictionaryLiteral {
    typealias Key = String
    typealias Value = Any
}

extension JSONHTTPBody: ExpressibleByArrayLiteral {
    typealias Element = Any
}
protocol Request {
    associatedtype HTTPBody: APIKit.HTTPBody
    var body: HTTPBody? { get }
    ...
}

Independent from Objective-C runtime

Refactor implementation that uses APIs only available on Objective-C runtime. For example, objc_getAssociatedObject() and objc_setAssociatedObject.

Linux support

🐧

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment