たとえば、以下のような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):
...
}
}