SwiftTask（Promise拡張）を使う

前回の

1. [Swiftで有限オートマトン（ステートマシン）を作る - Qiita](http://qiita.com/inamiy/items/cd218144c90926f9a134)
2. [Swift＋有限オートマトンでPromiseを拡張する - Qiita](http://qiita.com/inamiy/items/d3579b55a3ecc28dde63)

の続きです。今回は、拙作の「SwiftTask」というライブラリの紹介です。

SwiftTask
https://github.com/inamiy/SwiftTask

## SwiftTaskについて

SwiftTaskは、[PromiseKit](https://github.com/mxcl/PromiseKit)や[Bolts-iOS](https://github.com/BoltsFramework/Bolts-iOS)と同じく、Promiseライクなフロー制御ライブラリです。
主に以下の特徴があります。

- `Progress`、`Pause`/`Resume`、`Cancel`のインターフェースを新たに追加（Promiseを高機能化）
- ジェネリクスを活用し、ほぼ Pure Swift （Cocoa Frameworkに依存しない）
- SwiftTask単体は **非同期（スレッド、イベントループ）処理を持たない、薄いラッパークラス**
	- 基本的に、非同期処理はタスク内部（初期化クロージャ）で行う（後述のAlamofireの例を参考）
	- 複数タスクにまたがる協調的な処理（`Task.all()`など）では、最低限の排他制御のみを行う
- 引数1つのtrailing closureを使った、簡易的な method chaining

他ライブラリとの比較は、SwiftTask自身に非同期処理がないため簡単ではありませんが、参考までに表にします（2014/08/30現在。個人的な感想です）

| ライブラリ        | progress               | pause                  | cancel          | 直列処理               | 協調的処理           |
|-------------------|:-----------------------:|:----------------------:|:---------------:|:----------------------:|:--------------------:|
| NSOperation/Queue | ×                       | △<br>実行中は止まらない | ○               | ×<br>addDependencyつらい | ×<br>addDependencyつらい |
| PromiseKit        | ×                       | ×                      | ×               | ○<br>thenの実装が不十分？ | △<br>whenのみ       |
| Bolts-iOS         | ×                       | ×                      | ○<br>外部token方式 | ○                    | △<br>allのみ            |
| SwiftTask         | △<br>インターフェースのみ | △<br>インターフェースのみ | △<br>インターフェースのみ | ○             | ○<br>all, any, someなど |


## 使用例

SwiftTaskでタスクを定義する際（初期化クロージャ内）、直接Grand Central Dispatch等を使って非同期処理を行うことも可能です。
が、[Alamofire](https://github.com/Alamofire/Alamofire)（ネットワークライブラリ）に代表されるような、

- `obj.setProgressHandler()` (callback)
- `obj.setCompletionHandler()` (callback)
- `obj.pause()`
- `obj.resume()`
- `obj.cancel()`

のAPIの形に「別クラスで一旦ラップする」方が、より簡単に実装することができます。

### Alamofireを使った例

```swift
typealias Progress = (bytesWritten: Int64, totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64)

// define task
let task = Task<Progress, NSData?, NSError> { (progress, fulfill, reject, configure) in

    let request = Alamofire.download(.GET, "http://httpbin.org/stream/100", destination: somewhere)
    
    request.progress { (bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) in

        progress((bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) as Progress)

    }.response { (request, response, data, error) in

        if let error = error {
            reject(error)
            return
        }

        fulfill(data as NSData?)

    }

    configure.pause  = { request.suspend() }
    configure.resume = { request.resume() }
    configure.cancel = { request.cancel() }

    return
}
```

上のコードは、`NSURLSessionDownloadTask`を内部で使った、簡単なダウンロード処理のサンプルです。

`task`を初期化するにあたり、まずジェネリクスで表記された`Progress`、`Value (fulfilled)`、`Error (rejected)`の型を決める必要があります（Progressは必ずしも、0.0〜1.0までのFloat型などである必要はありません）。

```swift
Task<Progress, Value, Error>
```

続いて、初期化クロージャを使って、引数で渡される`progress`、`fulfill`、`reject`、`configure`を内部で実行します。このとき、

- `fulfill`と`reject`の呼び出し方は、従来のPromiseの時と同じ
- `progress`は、`fulfill` or `reject`を呼ぶまで、何回も実行できる（optional）
- `configure`で、`pause`、`resume`、`cancel`を実装する（optional）

なお、Alamofireは、元々Cocoaがデリゲートメソッドで用意しているprogress部分を **コールバック形式に変換している** ので、Alamofire → SwiftTaskへのprogressの伝搬が上述のように非常に簡単に書けます。


### Method Chaining

Promiseのようにmethod chainingをする方法は、次の通りです。

```swift
task.progress { progress in
    
    println("bytesWritten = \(progress.bytesWritten)")
    println("totalBytesWritten = \(progress.totalBytesWritten)")
    println("totalBytesExpectedToWrite = \(progress.totalBytesExpectedToWrite)")
    
}.then { (value: NSData?) -> Void in 
    
    println("fulfilled: \(value)")

}.catch { (errorInfo: ErrorInfo) -> Void in

    println("rejected: \(errorInfo.error) \(errorInfo.isCancelled)")
    
}
```

#### progress

まず、`task.progress(progressClosure)`を実行すると、`task`に`progressClosure`が登録されます。
そして、先ほどの初期化クロージャ内で引数の`progress`ハンドラが実行される度に、初期化クロージャの外側に位置する`progressClosure`が実行される仕組みになっています。
ちなみに、`task.progress()`の返り値は、同じ`task`なので、そのまま後続の`then`/`catch`につなぐことができます。

#### then

`task.then(closure)`は、 **1つの`closure`引数のみを受け取り、新しいtaskを返す**メソッドです。
基本的に、`task`が *fulfilled* になった場合は、`closure`が必ず呼ばれます。

ところで、JavaScript Promiseの場合、

1. `promise.then(onFulfilled)` (*fulfilled* only)
2. `promise.then(onFulfilled, onRejected)` (*fulfilled* & *rejected*)

のように、最大2つの引数を取りながら両者を区別していますが、SwiftTaskでは **引数を1つのclosureに制限＋オーバーロード＋型推論** して区別しています。このメリットとして、

- いつでも`task.then {...}.then {...}`の形で書ける（`()`がいらない）
- `onFulfilled`/`onRejected`に分けるより、Apple流の`completionHandler`に統一した方が、APIデザイン的に良い
	- 参考： [Stop Using Success/Failure Blocks | Collin Donnell](http://collindonnell.com/2013/04/07/stop-using-success-failure-blocks/)

があります。

`then`が受け取る`closure`の型の詳細については、[README.md](https://github.com/inamiy/SwiftTask/blob/master/README.md#taskthen_-closure---newtask)をご覧ください。

#### catch

`then`と同様、`task.catch(closure)`は、 1つの`closure`引数を受け取り、新しいtaskを返します。
`task`が *rejected* になった場合のみ、`closure`が呼ばれます。

ここで、`closure`の引数は`Error`オブジェクトではなく、`Task.ErrorInfo`という`(error: Error?, isCancelled: Bool)`のTuple型になっています。
`errorInfo.isCancelled`を見ることによって、キャンセルによるrejectedかどうかの判断ができます。

### Pause/Resume/Cancel

初期化クロージャの`configure`で設定した`pause`/`resume`/`cancel`は、外側から`task`オブジェクトの各メソッドを直接呼ぶことで実行できます。

```swift
task.pause()
task.resume()
task.cancel() // task.cancel(error)もできる
```

キャンセルされた`task`は、内部でrejectされたものとほぼ同じ形で、`catch`などで捕捉することができます。


## 番外：Custom Operatorを使う

まだ試験的な実装ですが、`progress`、`then`、`catch`をカスタム演算子に置き換えることもできます。

- `task ~ {...}` = `task.progress { progress in ...}`
- `task >>> {...}` = `task.then { value, errorInfo in ...}` (fulfilled & rejected)
- `task *** {...}` = `task.then { value in ...}` (fulfilled only)
- `task !!! {...}` = `task.catch { errorInfo in ...}` (rejected only)

さっそく試してみましょう。

```swift
task ~ { progress in
    ...
} *** { (value: NSData?) -> String in 
    ...
} !!! { (errorInfo: ErrorInfo) -> String in
    ...
} >>> { (value, errorInfo) -> Void in
    ...
}
```

<del>Ship it!:shipit:</del> `then`の処理の違いが、より見やすくなりました。
こちらも便利だと思うので、ぜひ使ってみてください。
（別の記法でもっと良いものがあったら、教えてください）