APIKitとCodableとの連携

  • 12
    いいね
  • 0
    コメント

たとえば、以下のようなJSONを返却するAPIをAPIKitSwift4で新しく追加されたCodableを組み合わせて実装する。

users.json
{
    "id": 1,
    "login": "starwars",
    "url": "https://example.com/starwars",
}

APIKitとCodableを組み合わせる場合の注意

JSONDecoder#decodeData型を引数にとる。

JSONをパースするAPIKit組み込みのJSONDataParserは内部でJSONSerialization.jsonObjectを利用しており、戻り値はルートオブジェクトがDictionaryArrayとなる。

したがってパースされた値をモデルオブジェクトに変換する

func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response

の引数objectData型ではなくなる。

Data型の値を返すJSONDataParserの作成

JSONDecoder#decodeの引数にレスポンスのJSONを引き渡せるようにData型の値を返すDataParserプロトコルに準拠したパーサオブジェクトを作成する。

  • DataParserプロトコル
DataParser.swift
public protocol DataParser {
    var contentType: String? { get }
    func parse(data: Data) throws -> Any
}

Data型の値をそのまま返すDataParserを作成する。

JSONDataParser.swift
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モデルを作成。

User.swift
struct User: Codable {
    let id: Int
    let login: String
    let url: String
}

これまでの内容を踏まえRequestプロトコルを適用したリクエストオブジェクトを作成する。

UserRequest.swift
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)
    }
}

リクエストの実行

リクエストを実行するとJSONDecoderCodableによってマッピングされたモデルオブジェクトを手軽に取得できる。

let request = UserRequest(name: "starwars")

Session.send(request) { result in
    switch result {
    case .success(let response):
        // responseはUserオブジェクト
    case .failure(let error):
        ...
    }
}