これまでは問題なかったんですが、機能の追加でAPIリクエストを全部待ってから次の処理をしたくなりました。なのでPromise系のライブラリを導入して、きちんと待機することにしました。
Promise系のライブラリはいくつかあるようです。
代表格はスター数、10,000越えのPromiseKitか。
でもREADMEを読んでしっくり来たのはこっちだったので、Hydraというのを使いました。
まず既存コード
かなりメタ化しちゃってますが、既存コードはこんな感じでした。
1func reload() {
2 API.getData1() { data1 in
3 if let data = data1 {
4 Data.setData1(data)
5 }
6 }
7
8 API.getData2() { data2 in
9 if let data = data2 {
10 Data.setData2(data)
11 }
12 }
13}
元々はこれで問題ありませんでした。ですが、全てのAPIが返ってきたあと次のページへ遷移するケースが発生しました。
きちんと全API呼び出しを待たないと、データが半端な状態でページ遷移をしてしまうことになります。Promiseの出番です。
Hydraを導入する
cocoapodsで導入しました。プロジェクトがSwift3系だったので、pod 'HydraAsync', '~> 1.0.2'して導入します。
API呼び出しを書き換える
元々のコードを次のように分離します。
1import Hydra
2
3func reload() {
4 let promise1 = getData1()
5 let promise2 = getData2()
6}
7
8func getData1() -> Promise<Void> {
9 return Promise<Void>(in: .background, token: nil) { (resolve, reject, _) in
10 API.getData1() { data1 in
11 if let data = data1 {
12 Data.setData1(data)
13 resolve()
14 } else {
15 reject("error")
16 }
17 }
18 }
19}
20
21func getData2() -> Promise<Void> {
22 // 略...
23}
Promise<Void>の最初の引数はiOSのDispatchQueue関連に対応するenumです。多くは.backgroundになるんじゃないかな。
2番目はInvalidationTokenでキャンセルするときに使うっぽい。今回は要らないから使ってなくて、よくわかってない。今回はnilにしてます。
最後のBlockがPromiseの実装を書くところ。
READMEのCreate a Promiseを参考に、元々のAPI呼び出しを移植してresolveとrejectを入れました。
今回はこの箇所でのみPromiseを使いたかっただけなので、API呼び出し自体をPromiseを返す実装に変更してしまうのはやめました。他の箇所への影響を考えてのことです。
逆にいうとAPI呼び出ししてPromiseを返す関数を1個挟むだけで使えるようになるので、小さく試すには便利。
完了を待つ
これはドキュメントのallの項目のとおり。
さっきの例だと、
1func reload() {
2 let promises = [getData1(), getData2()]
3 all(promises).then { _ in
4 self.doSomething()
5 }.catch { err in
6 }
7}
こうなります。すっきり書けました。