Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
APIKit
Search
M
October 21, 2017
Programming
0
92
APIKit
APIKitについての説明
M
October 21, 2017
Tweet
Share
More Decks by M
See All by M
Combine
matsuda
0
69
Swift 5.1 - Property Wrapper
matsuda
0
40
Swift 5.1
matsuda
0
31
Build Time Analyzer
matsuda
0
150
SafeArea
matsuda
0
52
URLProtocol
matsuda
0
63
Other Decks in Programming
See All in Programming
Multi Step Form, Decentralized Autonomous Organization
pumpkiinbell
1
750
Ruby on cygwin 2025-02
fd0
0
150
法律の脱レガシーに学ぶフロントエンド刷新
oguemon
5
740
富山発の個人開発サービスで日本中の学校の業務を改善した話
krpk1900
4
390
データベースのオペレーターであるCloudNativePGがStatefulSetを使わない理由に迫る
nnaka2992
0
150
2,500万ユーザーを支えるSREチームの6年間のスクラムのカイゼン
honmarkhunt
6
5.3k
Rubyで始める関数型ドメインモデリング
shogo_tksk
0
110
Lottieアニメーションをカスタマイズしてみた
tahia910
0
130
Rails アプリ地図考 Flush Cut
makicamel
1
120
XStateを用いた堅牢なReact Components設計~複雑なClient Stateをシンプルに~ @React Tokyo ミートアップ #2
kfurusho
1
910
負債になりにくいCSSをデザイナとつくるには?
fsubal
10
2.4k
Software Architecture
hschwentner
6
2.1k
Featured
See All Featured
Mobile First: as difficult as doing things right
swwweet
223
9.3k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
100
18k
Gamification - CAS2011
davidbonilla
80
5.1k
Visualization
eitanlees
146
15k
Statistics for Hackers
jakevdp
797
220k
Product Roadmaps are Hard
iamctodd
PRO
50
11k
Bootstrapping a Software Product
garrettdimon
PRO
306
110k
The Success of Rails: Ensuring Growth for the Next 100 Years
eileencodes
44
7k
Six Lessons from altMBA
skipperchong
27
3.6k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
45
9.4k
Done Done
chrislema
182
16k
Producing Creativity
orderedlist
PRO
344
39k
Transcript
APIKit
これまで利用してきたネットワー クライブラリ NSURLConnection Three20(Facebook) ASIHTTPRequest AFNetworking Swift 発表 Alamo re
APIKit
Type-safe networking abstraction layer that associates request type with response
type. -- APIKit “ “
Why APIKit?
作者が日本人
これまでいくつかPR してきましたが
作者が日本人なら気軽に日本語とか で質問できそうじゃないですか...?
実際にいくつかAPIKit にissue 挙げたりやPR してま す。
本題
特徴 レスポンスはモデルオブジェクトとして受け取れ る。 個々 のエンドポイントに対する定義が1 箇所で済 む。 成功時にレスポンスを非オプショナルな値で受け 取れる。 また失敗時にエラー
を非オプショナルな 値で受け取れる。(Result(Either) 型) -- Appendix 参照
API 定義 デー タ形式(JSON, XML, …) 認証有無 ステー タスコー ド
エラー レスポンス エンドポイント HTTP メソッド リクエストパラメー タ レスポンス etc.
API 定義 デー タ形式(JSON, XML, …) 認証有無 ステー タスコー ド
エラー レスポンス エンドポイント HTTP メソッド リクエストパラメー タ レスポンス etc.
実装(v3.1.2)
API 仕様( 例) エンドポイント:https://api.example.com/users HTTP メソッド:Get リクエストパラメー タ:name - String
デー タ形式:JSON レスポンス: { "id": 1, "login": "starwars", "url": "https://example.com/starwars", }
レスポンス struct User { let id: Int let login: String
let url: String init(JSON: Any) throws { ... } }
リクエストの定義 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
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) } }
パラメー タの定義 struct UsersRequest: APIKit.Request { ... let name: String
var parameters: Any? { return ["name": name] } } 各パラメー タがType-safe UsersRequest(name: "starwars")
例えば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] ...
リクエストの呼び出し 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) } }
その他の機能
URL クエリー var queryParameters: [String: Any]? { get } HTTP
body パラメー タ var bodyParameters: BodyParameters? { get } HTTP ヘッダー var headerFields: [String: String] { get } Content-Type に紐づくパー サー var dataParser: DataParser { get }
リクエストのカスタマイズ func intercept(urlRequest: URLRequest) throws -> URLRequest レスポンスのカスタマイズ レスポンスヘッダー を取り出すなど
func intercept(object: Any, urlResponse: HTTPURLResponse) throws -> Any
JSON パー サと組み合わせる
APIKit + Himotoki
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" ) } }
型制約つきprotocol extensions extension APIKit.Request where Response: Himotoki.Decodable { func response(from
object: Any, urlResponse: HTTPURLResponse ) throws -> Response { return try decodeValue(object) } }
APIKit + Decodable (Codable of Swift4)
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 を実装すれば問題ない。
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) }
上記でパー スされた値をモデルオブジェクトに変換す る際に利用される 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
Data 型を返すJSONDataParser を作成 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 } }
型制約つき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) } }
APIKit + Unbox
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") } }
型制約つき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 のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合
APIKit + ObjectMapper
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
型制約つきprotocol extensions extension Request where Response: ImmutableMappable { func response(from
object: Any, urlResponse: HTTPURLResponse) throws -> Response { return try Response(JSONObject: object) } } ※ JSON のルー トオブジェクトがオブジェクト値( ハッシュ) の 場合
Demo
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", }
まとめ API の仕様をRequest プロトコルに適合させていく のが、 Swift という言語に翻訳している感覚 各エンドポイントの定義が一箇所にまとまるので あとから把握しやすい 型制約付きprotocol
extensions を利用してモデル オブジェクトへのマッピングを簡潔にできる 作者が日本人だから質問への障壁が低い? 例えば日本語で質問したり
でもちゃんとがっつり英語でPR してます
Appendix 堅牢で使いやすいAPI クライアントをSwift で実装 したい APIKit でSwift らしいAPI クライアントを実装する #potatotips
でAPIKit を紹介してきた Swift 2 でのAPIKit + Himotoki APIKit: レスポンスに応じた独自のエラー を投げる