Upgrade to Pro — share decks privately, control downloads, hide ads and more …

APIKit

M
October 21, 2017

 APIKit

APIKitについての説明

M

October 21, 2017
Tweet

More Decks by M

Other Decks in Programming

Transcript

  1. API 定義 デー タ形式(JSON, XML, …) 認証有無 ステー タスコー ド

    エラー レスポンス エンドポイント HTTP メソッド リクエストパラメー タ レスポンス etc.
  2. API 定義 デー タ形式(JSON, XML, …) 認証有無 ステー タスコー ド

    エラー レスポンス エンドポイント HTTP メソッド リクエストパラメー タ レスポンス etc.
  3. API 仕様( 例) エンドポイント:https://api.example.com/users HTTP メソッド:Get リクエストパラメー タ:name - String

    デー タ形式:JSON レスポンス: { "id": 1, "login": "starwars", "url": "https://example.com/starwars", }
  4. レスポンス struct User { let id: Int let login: String

    let url: String init(JSON: Any) throws { ... } }
  5. リクエストの定義 Request プロトコルに適合させ、 最低5 つの項目を実装 する associatedtype Response var baseURL:

    URL { get } var method: HTTPMethod { get } var path: String { get } func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response
  6. struct UsersRequest: APIKit.Request { typealias Response = User var baseURL:

    URL { return URL(string: "https://api.example.com")! } var method: APIKit.HTTPMethod { return .get } var path: String { return "/users" } func response(from object: Any, urlResponse: HTTPURLResponse ) throws -> User { return try User(JSON: object) } }
  7. パラメー タの定義 struct UsersRequest: APIKit.Request { ... let name: String

    var parameters: Any? { return ["name": name] } } 各パラメー タがType-safe UsersRequest(name: "starwars")
  8. 例えばAlamofire を利用した場合 /// public typealias Parameters = [String: Any] let

    parameters: Parameters = ["name": "starwars"] Alamofire.request("https://api.example.com/users", parameters: parameters) [String: Any] のDictionary にパラメー タをセットする ので、 期待している型ではない値をセットできてしま う。 let parameters: Parameters = ["name": 2] ...
  9. リクエストの呼び出し let request = UsersRequest(name: "starwars") Session.send(request) { result in

    switch result { case .success(let response): print("response >>>", response) case .failure(let error): print("error >>>", error) } }
  10. URL クエリー var queryParameters: [String: Any]? { get } HTTP

    body パラメー タ var bodyParameters: BodyParameters? { get } HTTP ヘッダー var headerFields: [String: String] { get } Content-Type に紐づくパー サー var dataParser: DataParser { get }
  11. Himotoki.Decodable の適用 extension User: Himotoki.Decodable { static func decode(_ e:

    Extractor) throws -> User { return try User( id: e <| "id", login: e <| "login", url: e <| "url" ) } }
  12. 型制約つきprotocol extensions extension APIKit.Request where Response: Himotoki.Decodable { func response(from

    object: Any, urlResponse: HTTPURLResponse ) throws -> Response { return try decodeValue(object) } }
  13. Decodable(Codable) の適用 struct User: Codable { let id: Int let

    login: String let url: String } ちなみにCodable をextension で適用するとエラー にな る。 自分で public func encode(to encoder: Encoder) throws public init(from decoder: Decoder) throws を実装すれば問題ない。
  14. APIKit 組み込みのJSON パー サの問題 APIKit に組み込みのJSON パー サJSONDataParser は内 部でJSONSerialization.jsonObject(with:options:)

    を利 用しており、 戻り値はルー トオブジェクトが Dictionary やArray の値 // APIKit.JSONDataParser public func parse(data: Data) throws -> Any { ... return try JSONSerialization.jsonObject( with: data, options: readingOptions) }
  15. 上記でパー スされた値をモデルオブジェクトに変換す る際に利用される func response(from object: Any, urlResponse: HTTPURLResponse) throws

    -> Response の引数object はDictionary やArray パー スされた値をモデルオブジェクトに変換する JSONDecoder#decode(_:from:) はData 型を引数にとる。 func decode<T>(_ type: T.Type, from data: Data) throws -> T where T : Decodable
  16. Data 型を返すJSONDataParser を作成 DataParser プロトコル public protocol DataParser { var

    contentType: String? { get } func parse(data: Data) throws -> Any }
  17. Data 型の値をそのまま返すDataParser class JSONDataParser: APIKit.DataParser { var contentType: String? {

    return "application/json" } func parse(data: Data) throws -> Any { return data } }
  18. 型制約つき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) } }
  19. Unboxable の適用 extension User: Unboxable { init(unboxer: Unboxer) throws {

    self.id = try unboxer.unbox(key: "id") self.login = try unboxer.unbox(key: "login") self.url = try unboxer.unbox(key: "url") } }
  20. 型制約つきprotocol extensions extension Request where Response: Unboxable { func response(from

    object: Any, urlResponse: HTTPURLResponse) throws -> Response { guard let dictionary = object as? UnboxableDictionary throw ResponseError.unexpectedObject(object) } return try unbox(dictionary: dictionary) } } ※ JSON のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合
  21. Mappable(ImmutableMappable) の適用 extension User: ImmutableMappable { init(map: Map) throws {

    id = try map.value("id") login = try map.value("login") url = try map.value("url") } mutating func mapping(map: Map) { id >>> map["id"] login >>> map["login"] url >>> map["url"] } } ※ 定数プロパティの場合はMappable プロトコルは使えない ImmutableMappable はBeta
  22. 型制約つきprotocol extensions extension Request where Response: ImmutableMappable { func response(from

    object: Any, urlResponse: HTTPURLResponse) throws -> Response { return try Response(JSONObject: object) } } ※ JSON のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合
  23. API 仕様( 例) エンドポイント:https://api.example.com/articles HTTP メソッド:Get リクエストパラメー タ:page - Int

    デー タ形式:JSON レスポンス: { "id": 1, "title": "Star Wars", "created_at": "2000-01-01T00:00:00+00:00", }
  24. まとめ API の仕様をRequest プロトコルに適合させていく のが、 Swift という言語に翻訳している感覚 各エンドポイントの定義が一箇所にまとまるので あとから把握しやすい 型制約付きprotocol

    extensions を利用してモデル オブジェクトへのマッピングを簡潔にできる 作者が日本人だから質問への障壁が低い? 例えば日本語で質問したり
  25. Appendix 堅牢で使いやすいAPI クライアントをSwift で実装 したい APIKit でSwift らしいAPI クライアントを実装する #potatotips

    でAPIKit を紹介してきた Swift 2 でのAPIKit + Himotoki APIKit: レスポンスに応じた独自のエラー を投げる