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
91
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
140
SafeArea
matsuda
0
51
URLProtocol
matsuda
0
63
Other Decks in Programming
See All in Programming
Jakarta EE meets AI
ivargrimstad
0
240
テストケースの名前はどうつけるべきか?
orgachem
PRO
0
130
あれやってみてー駆動から成長を加速させる / areyattemite-driven
nashiusagi
1
200
PHPUnitしか使ってこなかった 一般PHPerがPestに乗り換えた実録
mashirou1234
0
130
선언형 UI에서의 상태관리
l2hyunwoo
0
150
Effective Signals in Angular 19+: Rules and Helpers @ngbe2024
manfredsteyer
PRO
0
130
生成AIでGitHubソースコード取得して仕様書を作成
shukob
0
330
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
320
テストコード文化を0から作り、変化し続けた組織
kazatohiei
2
1.5k
ゆるやかにgolangci-lintのルールを強くする / Kyoto.go #56
utgwkk
1
370
ドメインイベント増えすぎ問題
h0r15h0
1
250
HTTP compression in PHP and Symfony apps
dunglas
2
1.7k
Featured
See All Featured
Statistics for Hackers
jakevdp
796
220k
Thoughts on Productivity
jonyablonski
67
4.4k
Building Your Own Lightsaber
phodgson
103
6.1k
Practical Orchestrator
shlominoach
186
10k
KATA
mclloyd
29
14k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
232
17k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
44
9.3k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
Build The Right Thing And Hit Your Dates
maggiecrowley
33
2.4k
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
Agile that works and the tools we love
rasmusluckow
328
21k
Become a Pro
speakerdeck
PRO
26
5k
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: レスポンスに応じた独自のエラー を投げる