たとえば、以下のようなJSON
を返却するAPIをAPIKit
と Swift4で新しく追加されたCodable
を組み合わせて実装する。
{
"id": 1,
"login": "starwars",
"url": "https://example.com/starwars",
}
APIKitとCodableを組み合わせる場合の注意
JSONDecoder#decode
はData
型を引数にとる。
JSON
をパースするAPIKit
組み込みのJSONDataParser
は内部でJSONSerialization.jsonObject
を利用しており、戻り値はルートオブジェクトがDictionary
やArray
となる。
したがってパースされた値をモデルオブジェクトに変換する
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response
の引数object
はData
型ではなくなる。
Data
型の値を返すJSONDataParser
の作成
JSONDecoder#decode
の引数にレスポンスのJSON
を引き渡せるようにData
型の値を返すDataParser
プロトコルに準拠したパーサオブジェクトを作成する。
-
DataParser
プロトコル
public protocol DataParser {
var contentType: String? { get }
func parse(data: Data) throws -> Any
}
Data
型の値をそのまま返すDataParser
を作成する。
class JSONDataParser: APIKit.DataParser {
var contentType: String? {
return "application/json"
}
func parse(data: Data) throws -> Any {
// ここではデコードせずにそのまま返す
return data
}
}
Request
プロトコルのfunc response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response
を実装する。
Codable
を適用したUser
モデルを作成。
struct User: Codable {
let id: Int
let login: String
let url: String
}
これまでの内容を踏まえRequest
プロトコルを適用したリクエストオブジェクトを作成する。
struct UserRequest: APIKit.Request {
typealias Response = User
// MARK: - APIKit.Request
var baseURL: URL { ... }
var method: APIKit.HTTPMethod { ... }
...(省略)...
// 作成したJSONDataParserをパーサとして適用する
// (APIKit.JSONDataParserではない)
var dataParser: DataParser {
return JSONDataParser()
}
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> User {
let decoder = JSONDecoder()
return try decoder.decode(User.self, from: object as! Data)
}
}
もしくは個々のリクエストに実装せずに、型制約つきprotocol extensionsを利用する。
extension APIKit.Request where Response: Decodable {
// 作成したJSONDataParserをパーサとして適用する
var dataParser: DataParser {
return JSONDataParser()
}
func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response {
let decoder = JSONDecoder()
return try decoder.decode(Response.self,
from: object as! Data)
}
}
リクエストの実行
リクエストを実行するとJSONDecoder
とCodable
によってマッピングされたモデルオブジェクトを手軽に取得できる。
let request = UserRequest(name: "starwars")
Session.send(request) { result in
switch result {
case .success(let response):
// responseはUserオブジェクト
case .failure(let error):
...
}
}