PIYO - Tech & Life -

Hydraを使ってSwiftでPromiseする

Swift Promise

これまでは問題なかったんですが、機能の追加でAPIリクエストを全部待ってから次の処理をしたくなりました。なのでPromise系のライブラリを導入して、きちんと待機することにしました。

Promise系のライブラリはいくつかあるようです。

代表格はスター数、10,000越えのPromiseKitか。

GitHub - mxcl/PromiseKit: Promises for Swift & ObjC.
Promises for Swift & ObjC. Contribute to mxcl/PromiseKit development by creating an account on GitHub.

でもREADMEを読んでしっくり来たのはこっちだったので、Hydraというのを使いました。

GitHub - malcommac/Hydra: ⚡️ Lightweight full-featured Promises, Async & Await Library in Swift
⚡️ Lightweight full-featured Promises, Async & Await Library in Swift - GitHub - malcommac/Hydra: ⚡️ Lightweight full-featured Promises, Async & Await Library in Swift

まず既存コード

かなりメタ化しちゃってますが、既存コードはこんな感じでした。

func reload() {
  API.getData1() { data1 in
    if let data = data1 {
      Data.setData1(data)
    }
  }

  API.getData2() { data2 in
    if let data = data2 {
      Data.setData2(data)
    }
  }
}

元々はこれで問題ありませんでした。ですが、全てのAPIが返ってきたあと次のページへ遷移するケースが発生しました。

きちんと全API呼び出しを待たないと、データが半端な状態でページ遷移をしてしまうことになります。Promiseの出番です。

Hydraを導入する

cocoapodsで導入しました。プロジェクトがSwift3系だったので、pod 'HydraAsync', '~> 1.0.2'して導入します。

API呼び出しを書き換える

元々のコードを次のように分離します。

import Hydra

func reload() {
  let promise1 = getData1()
  let promise2 = getData2()
}

func getData1() -> Promise<Void> {
  return Promise<Void>(in: .background, token: nil) { (resolve, reject, _) in
    API.getData1() { data1 in
      if let data = data1 {
        Data.setData1(data)
        resolve()
      } else {
        reject("error")
      }
    }
  }
}

func getData2() -> Promise<Void> {
  // 略...
}

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

さっきの例だと、

func reload() {
  let promises = [getData1(), getData2()]
  all(promises).then { _ in
    self.doSomething()
  }.catch { err in
  }
}

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

RELATED

SwiftでJSのsetTimeoutみたいなことをする

iOSで仕方なく少し待ってから処理を実行するてのを時々やりたくなるんですが、毎度書き方を忘れるので、自分用にメソッドを定義して使っています。 どこかグローバルなところに定義しとくとアプリ全体で使えるので

UIImageの明るさをCIFilterで調整するSwiftコード

CIFilterを使う CIColorControls inputBrightnessに値を設定 値は−1.0〜1.0 import CoreImage extention UIImage { func adjustBrightness(brightness: Double) -> UIImage? { let ciImage = CIImage(cgImage: self.cgImage!) let filter = CIFilter(name: "CIColorControls") filter?.setValue(ciImage, forKey: kCIInputImageKey) filter?.setValue(brightness, forKey: "inputBrightness") guard let newCIImage = filter?.outputImage else { return nil } let context = CIContext(options: nil) guard let cgImage = context.createCGImage(newCIImage, from: newCIImage.extent) else { return nil } return UIImage(cgImage: cgImage, scale: