swift3で作ったiOSアプリでサーバーへHTTPリクエストを複数回投げる場合に、 普通に作ると、正常ケース、異常ケースの処理があちこちにばらけて、ソースが汚くなります。
PromiseKitを使ったところ、ソースが短く、処理も追いやすくて素晴らしかったので、メモを残しておきます。
ここでは以下のような処理フローを想定します。
1) ブログ記事のテキストをポストする(request)
2) 生成したブログ記事のIDをjsonで返す(response)
3) 投稿したブログ記事のIDに対して画像を2つポストする(request)
画像ポスト処理は並列に行っても良い。
4) 2つの画像ポストが両方成功したらブログ生成処理正常完了として画面を更新する。
5) いずれかのHTTPの応答が失敗(404エラー等)した場合と、
応答のJSONが意図した内容でなかった場合は、ブログ生成失敗として画面を更新する。
HTTPリスエストを行う部分は、URLSession、Alamofire、APIKit なんでも良いです。
正常応答の場合に渡す情報を持つクラスTと異常応答の場合に渡すエラー情報の2つが必要となります。 クラスTの部分はリクエスト毎に別のクラスを割り当てることができます。 エラー情報はErrorを継承したクラスならなんでも良いですが、ここでは以下のようなenumとしました。 私が使っているHTTPリクエストライブラリは古くてNSErrorをHTTPResponseのエラーとして返すので、 このようにしました。
enum APIError: Error { case HTTPResponseError(nserror:NSError) // 404エラー等 case JSONParseError // レスポンスJSONのパースに失敗 }
そして、メインの処理は以下のような感じになります。
firstly { return Promise<[String:Any]> { fulfill, reject { self.sendRequest(r as URLRequest, completion: {(json: Any?, error:NSError?) in if let error = error { return reject(APIError.HTTPResponseError(nserror: error)) } else { if let json = json as? [String:Any] { return fulfill(json) } } return reject(APIError.JSONParseError) }) } }.then { json -> Promise<[Bool]> in if let blogid:Int = json["id"] as? Int { return when(fulfilled: [ self.postImagePromise(blog:blogid, photo: 1), self.postImagePromise(blog:blogid, photo: 2), ]) } else { return Promise<[Bool]>(error:APIError.JSONParseError) } }.then { r in self.showSuccess() }.catch { error in self.showFail() } func postImagePromise(blood:Int, photo:Int) -> Promise<Bool> { このタイミングで処理が成功したことを返す場合は return Promise<Bool>(value:true) とすればよい return Promise<Bool> { fulfill, reject in ここでreject(APIError.hoge)またはfulfill(true)をreturnする } }
HTTPでよくある処理を表現するのに適していてだいぶすっきりしています。
JSON周りや、Resultの処理などまだ改良の余地があるソースですが、PromiseKitとは関係ありません。
複数のAPIを投げる場合は、使うとソースのメンテナンスが上がる、むしろ使わないと機能追加やメンテで苦労しそうだと思いました。