Help us understand the problem. What is going on with this article?

APIKitとCodableとの連携

More than 1 year has passed since last update.

たとえば、以下のような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):
        ...
    }
}
matsuda
I am iOS and Ruby on Rails developer.
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした