Slide 1

Slide 1 text

APIKit

Slide 2

Slide 2 text

これまで利用してきたネットワー クライブラリ NSURLConnection Three20(Facebook) ASIHTTPRequest AFNetworking Swift 発表 Alamo re APIKit

Slide 3

Slide 3 text

Type-safe networking abstraction layer that associates request type with response type. -- APIKit “ “

Slide 4

Slide 4 text

Why APIKit?

Slide 5

Slide 5 text

作者が日本人

Slide 6

Slide 6 text

これまでいくつかPR してきましたが

Slide 7

Slide 7 text

作者が日本人なら気軽に日本語とか で質問できそうじゃないですか...?

Slide 8

Slide 8 text

実際にいくつかAPIKit にissue 挙げたりやPR してま す。

Slide 9

Slide 9 text

本題

Slide 10

Slide 10 text

特徴 レスポンスはモデルオブジェクトとして受け取れ る。 個々 のエンドポイントに対する定義が1 箇所で済 む。 成功時にレスポンスを非オプショナルな値で受け 取れる。 また失敗時にエラー を非オプショナルな 値で受け取れる。(Result(Either) 型) -- Appendix 参照

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

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

Slide 13

Slide 13 text

実装(v3.1.2)

Slide 14

Slide 14 text

API 仕様( 例) エンドポイント:https://api.example.com/users HTTP メソッド:Get リクエストパラメー タ:name - String デー タ形式:JSON レスポンス: { "id": 1, "login": "starwars", "url": "https://example.com/starwars", }

Slide 15

Slide 15 text

レスポンス struct User { let id: Int let login: String let url: String init(JSON: Any) throws { ... } }

Slide 16

Slide 16 text

リクエストの定義 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

Slide 17

Slide 17 text

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) } }

Slide 18

Slide 18 text

パラメー タの定義 struct UsersRequest: APIKit.Request { ... let name: String var parameters: Any? { return ["name": name] } } 各パラメー タがType-safe UsersRequest(name: "starwars")

Slide 19

Slide 19 text

例えば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] ...

Slide 20

Slide 20 text

リクエストの呼び出し 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) } }

Slide 21

Slide 21 text

その他の機能

Slide 22

Slide 22 text

URL クエリー var queryParameters: [String: Any]? { get } HTTP body パラメー タ var bodyParameters: BodyParameters? { get } HTTP ヘッダー var headerFields: [String: String] { get } Content-Type に紐づくパー サー var dataParser: DataParser { get }

Slide 23

Slide 23 text

リクエストのカスタマイズ func intercept(urlRequest: URLRequest) throws -> URLRequest レスポンスのカスタマイズ レスポンスヘッダー を取り出すなど func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any

Slide 24

Slide 24 text

JSON パー サと組み合わせる

Slide 25

Slide 25 text

APIKit + Himotoki

Slide 26

Slide 26 text

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" ) } }

Slide 27

Slide 27 text

型制約つきprotocol extensions extension APIKit.Request where Response: Himotoki.Decodable { func response(from object: Any, urlResponse: HTTPURLResponse ) throws -> Response { return try decodeValue(object) } }

Slide 28

Slide 28 text

APIKit + Decodable (Codable of Swift4)

Slide 29

Slide 29 text

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 を実装すれば問題ない。

Slide 30

Slide 30 text

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) }

Slide 31

Slide 31 text

上記でパー スされた値をモデルオブジェクトに変換す る際に利用される func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response の引数object はDictionary やArray パー スされた値をモデルオブジェクトに変換する JSONDecoder#decode(_:from:) はData 型を引数にとる。 func decode(_ type: T.Type, from data: Data) throws -> T where T : Decodable

Slide 32

Slide 32 text

Data 型を返すJSONDataParser を作成 DataParser プロトコル public protocol DataParser { var contentType: String? { get } func parse(data: Data) throws -> Any }

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

型制約つき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) } }

Slide 35

Slide 35 text

APIKit + Unbox

Slide 36

Slide 36 text

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") } }

Slide 37

Slide 37 text

型制約つき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 のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合

Slide 38

Slide 38 text

APIKit + ObjectMapper

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

型制約つきprotocol extensions extension Request where Response: ImmutableMappable { func response(from object: Any, urlResponse: HTTPURLResponse) throws -> Response { return try Response(JSONObject: object) } } ※ JSON のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合

Slide 41

Slide 41 text

Demo

Slide 42

Slide 42 text

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", }

Slide 43

Slide 43 text

まとめ API の仕様をRequest プロトコルに適合させていく のが、 Swift という言語に翻訳している感覚 各エンドポイントの定義が一箇所にまとまるので あとから把握しやすい 型制約付きprotocol extensions を利用してモデル オブジェクトへのマッピングを簡潔にできる 作者が日本人だから質問への障壁が低い? 例えば日本語で質問したり

Slide 44

Slide 44 text

でもちゃんとがっつり英語でPR してます

Slide 45

Slide 45 text

Appendix 堅牢で使いやすいAPI クライアントをSwift で実装 したい APIKit でSwift らしいAPI クライアントを実装する #potatotips でAPIKit を紹介してきた Swift 2 でのAPIKit + Himotoki APIKit: レスポンスに応じた独自のエラー を投げる