ETA: Early 2017
- 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 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
}
}
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 }
...
}
Refactor implementation that uses APIs only available on Objective-C runtime. For example, objc_getAssociatedObject()
and objc_setAssociatedObject
.
🐧