Promiseパターンでできること
Promiseパターンを使うと何ができるのかを調べてみました。
- 非同期で処理を行い、処理終了後に特定のメソッドを呼び出す
- 失敗時の処理を登録しておくことで、最初の処理が失敗した時にそのメソッドが呼ばれる
- 複数の非同期処理を順番に実行する
- 複数の非同期処理を並行に実行して、全て成功したタイミングでコールバックメソッドを呼ぶ
プロミスパターンでは上の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_promiseとthenInBackgroundはバックグラウンドで動きます。
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-finallyのfinallyに似たイメージです。
順番としてはエラー処理の前に呼ばれます。
// 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 }) })
複数の処理を並行して走らせる
下のように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()) })