しめ鯖日記

swift, iPhoneアプリ開発, ruby on rails等のTipsや入門記事書いてます

SwiftでPromiseパターンを実現するPromiseKitを使ってみる

Promiseパターンでできること

Promiseパターンを使うと何ができるのかを調べてみました。

  1. 非同期で処理を行い、処理終了後に特定のメソッドを呼び出す
  2. 失敗時の処理を登録しておくことで、最初の処理が失敗した時にそのメソッドが呼ばれる
  3. 複数の非同期処理を順番に実行する
  4. 複数の非同期処理を並行に実行して、全て成功したタイミングでコールバックメソッドを呼ぶ

プロミスパターンでは上の4つが特徴として上げられていました。
1と2は通信ライブラリやアニメーションのAPIに標準で付いている機能ですね。

3番はとても便利そうです。
複数の非同期処理を順番に実行する場合、「コールバックの中に処理を書いてそのコールバックに更に処理を…」みたいな大量のインデントを生み出してしまうのでそれを回避できるのが良さそうです。

4番もかなり使いどころが多そうです。
サーバーへ複数のリクエストを送って全部返って来たら特定の処理をするという時がかなり便利になりそうです。

PromiseKitをインストール

インストールはCocoaPodsで行います。
2015/10/3時点でSwift2.0に対応していなかったのでgithubから直接取得します。

use_frameworks!

pod "PromiseKit", git: "https://github.com/mxcl/PromiseKit.git", branch: "swift-2.0-beta5"

PromiseKitを使ってみる

下のように記述すれば1つ1つが順番に実行されます。
引数には前の処理の戻り値が入るようです。

dispatch_promise(body: { () -> Int in
    return 1
}).then({ value -> Int in
    print(value) // → 1
    return 2
}).thenInBackground({ value -> Int in
    print(value) // → 2
    return 3
})

dispatch_promisethenInBackgroundはバックグラウンドで動きます。

dispatch_promise(body: { () -> Int in
    print(NSThread.isMainThread()) // → false
    return 1
}).then({ value -> Int in
    print(NSThread.isMainThread()) // → true
    return 2
}).thenInBackground({ value -> Int in
    print(NSThread.isMainThread()) // → false
    return 3
})

PromiseKitでエラー処理

エラー処理はSwift2.0から追加されたtry-catchを使います。
下のようにerrorメソッドを書いておけば、エラー発生時にerrorメソッドが呼ばれます。

下の例では最初のブロックでエラーが出たので、2番目の処理を実行せずにerrorが呼ばれました。

enum MyError: ErrorType {
    case A
}

// 1 → ERROR!!! とログが表示される
dispatch_promise(body: { () -> Int in
    print(1)
    throw MyError.A
    return 1
}).then({ value -> Int in
    print(2)
    return 2
}).error({ body -> Void in
    print("ERROR!!!")
})

2番目のブロックでエラーが出た場合もerrorが呼ばれます。

// 1 → 2 → ERROR!!! とログが表示される
dispatch_promise(body: { () -> Int in
    print(1)
    return 1
}).then({ value -> Int in
    print(2)
    throw MyError.A
    return 2
}).error({ body -> Void in
    print("ERROR!!!")
})

もちろんエラーが起こらない場合はerrorは呼ばれません。

// 1 → 2 とログが表示される
dispatch_promise(body: { () -> Int in
    print(1)
    return 1
}).then({ value -> Int in
    print(2)
    return 2
}).error({ body -> Void in
    print("ERROR!!!")
})

エラーの有無に関わらず呼ばれるメソッド

alwaysというメソッドはエラーが起きても起きなくても呼ばれます。
try-catch-finallyfinallyに似たイメージです。

順番としてはエラー処理の前に呼ばれます。

// 1 → ALWAYS!!! → ERROR!!! とログが表示される
dispatch_promise(body: { () -> Int in
    print(1)
    throw MyError.A
    return 1
}).then({ value -> Int in
    print(2)
    return 2
}).always({
    print("ALWAYS!!!")
}).error({ body -> Void in
    print("ERROR!!!")
})

エラーが発生しない場合も呼ばれます。

// 1 → 2 → ALWAYS!!! とログが表示される
dispatch_promise(body: { () -> Int in
    print(1)
    return 1
}).then({ value -> Int in
    print(2)
    return 2
}).always({
    print("ALWAYS!!!")
}).error({ body -> Void in
    print("ERROR!!!")
})

PromiseKitでUIViewのアニメーション

下のようにUIViewのanimationメソッドを使えばアニメーションもPromiseパターンを使って実現できます。

let v = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
v.backgroundColor = UIColor.greenColor()
view.addSubview(v)

UIView.animate(duration: 10, delay: 1, animations: {
    v.frame.origin.x = 200
}).then({ body -> Promise<Bool> in
    return UIView.animate(animations: {
        v.frame.origin.y = 200
    })
}).then({ body -> Promise<Bool> in
    return UIView.animate(animations: {
        v.frame.origin.x = 100
    })
}).then({ body -> Promise<Bool> in
    return UIView.animate(animations: {
        v.frame.origin.y = 100
    })
})

f:id:llcc:20151003143322g:plain

複数の処理を並行して走らせる

下のようにwhen複数の処理の配列を入れれば並行して走ります。
thenの中の処理は、when内の全ての処理が終わってから実行されました。

let v = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
v.backgroundColor = UIColor.greenColor()
view.addSubview(v)

let animate1 = UIView.animate(duration: 10, animations: {
    v.frame.origin.x = 200
})
let animate2 = UIView.animate(animations: {
    v.frame.origin.y = 200
})
when([animate1, animate2]).then(body: { (result: [Bool]) -> AnyPromise in
    print(1)
    return AnyPromise(bound: Promise())
})

参考URL

非同期処理とPromise(Deferred)を背景から理解しよう - hifive PromiseKit