これまでは問題なかったんですが、機能の追加で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呼び出しを移植してresolverejectを入れました。

今回はこの箇所でのみPromiseを使いたかっただけなので、API呼び出し自体をPromiseを返す実装に変更してしまうのはやめました。他の箇所への影響を考えてのことです。

逆にいうとAPI呼び出ししてPromiseを返す関数を1個挟むだけで使えるようになるので、小さく試すには便利。

完了を待つ

これはドキュメントのallの項目のとおり。

Hydra#all

さっきの例だと、

1func reload() {
2  let promises = [getData1(), getData2()]
3  all(promises).then { _ in
4    self.doSomething()
5  }.catch { err in
6  }
7}

こうなります。すっきり書けました。