TOKOROM BLOG

iOSとかVimとかその他日々の雑多な技術情報

SwiftTask、PromiseKit、Boltsを比較する(2015年3月版) はてなブックマーク - SwiftTask、PromiseKit、Boltsを比較する(2015年3月版)

Permalink

  • ※2015/3/11時点での比較結果ですので、今後、各ライブラリともにパワーアップしていくと思われます
  • ※いまはできないことでも各ライブラリのIssuesでは実装の検討が進んでいるものも多くあるようです

次の案件で(Swiftで)Promiseライクなフロー制御を実現するために利用するライブラリを選定するため、2015/3/11時点の

を(表面だけ)使って比較してみました。

なお、昨年の7月時点では(Swiftで使うぶんには)PromiseKitが将来性があると判断し、しばらくはPromiseKitを使っていました。

その後、SwiftTaskも登場して気になっていたので、今回改めて新案件で採用するライブラリを選定したという経緯になります。

以下にそれぞれ使ってみた結果を紹介させていただきます。

更新頻度

この3つのライブラリはどれも更新頻度が多く、現在betaのSwift 1.2でも(別ブランチで)きちんと動く形になっています。

試すネタ

AlamofireでGenericにModelオブジェクトを取得する で試したAlamofireを使うコードをネタとしてそれぞれ3つのライブラリを適用してみました。

Taskを使うほうのコード

SwiftTask

1
2
3
4
5
request(.GET, urlString).success { [unowned self] (users: [User]) in
    self.textView?.text = "\(users)"
}.failure { [unowned self] error, _ in
    self.textView?.text = "\(error)"
}
  • すっきり直感的に書けます
  • Genericsで欲しいModelオブジェクトを直感的に指定するようなインターフェースにできます

PromiseKit

1
2
3
4
5
6
7
8
request(.GET, urlString).then { [unowned self] (users: [User]) -> Void in
    self.textView?.text = "\(users)"
    return
}.catch { [unowned self] error -> Void in
    self.textView?.text = "\(error)"
    return
}.finally {
}
  • おおむねすっきりと書けます
  • Genericsで欲しいModelオブジェクトを直感的に指定するようなインターフェースにできます
  • そのままだとSwiftの型推論がうまくいかなくて一見不要そうなreturnとかfinallyを加えて型推論を助けてあげる必要があるケースがあり、ちょっと癖があるかんじです

Bolts

1
2
3
4
5
6
7
8
request(Alamofire.Method.GET, urlString, User.self).continueWithBlock { (task: BFTask!) -> AnyObject! in
    if let error = task.error {
        self.textView?.text = "\(error)"
    } else if let users = task.result as? [User] {
        self.textView?.text = "\(users)"
    }
    return nil
}
  • すっきり直感的に書けます
  • Swift専用のインターフェースがないため、Genericsを使うには工夫が必要になります
    • 上の例だと引数にModelクラスのTypeを指定するようにしていますが、素直にGenericsを使えないのはちょっと残念です
    • TaskのResultはAnyObjectになってしまうので個別にダウンキャストなどが必要です

Taskを作るほうのコード

  • responseCollectionといったAlamofireの拡張は事前に済ませている前提です(参考
  • validationの個別カスタマイズなどには今回のサンプルでは対応していません

こちらについてはどのライブラリも分かりやすく大差はないかなと思います。

SwiftTask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// SwiftTaskのみprogress取得に対応したサンプルになっています
public func request<T: ResponseCollectionSerializable>(method: Alamofire.Method, URLString: Alamofire.URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: Alamofire.ParameterEncoding = .URL) -> Task<Progress, [T], NSError> {
    let task = Task<Progress, [T], NSError> { progress, fulfill, reject, configure in
        Alamofire.request(method, URLString, parameters: parameters, encoding: encoding)
            .progress { bytesWritten, totalBytesWritten, totalBytesExpectedToWrite in
                progress((bytesWritten, totalBytesWritten, totalBytesExpectedToWrite) as Progress)
            }
            .validate()
            .responseCollection { (request, response, collection: [T]?, error) in
                if let error = error {
                    reject(error)
                    return
                }
                if let collection = collection {
                    fulfill(collection)
                    return
                }
                reject(NSError()) //< TODO: unexpected error
            }
    }
    return task
}

PromiseKit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public func request<T: ResponseCollectionSerializable>(method: Alamofire.Method, URLString: Alamofire.URLStringConvertible, parameters: [String: AnyObject]? = nil, encoding: Alamofire.ParameterEncoding = .URL) -> Promise<[T]> {
    let promise = Promise<[T]>({ fulfiller, rejecter in
        Alamofire.request(method, URLString, parameters: parameters, encoding: encoding)
            .validate()
            .responseCollection { (request, response, collection: [T]?, error) in
                if let error = error {
                    rejecter(error)
                    return
                }
                if let collection = collection {
                    fulfiller(collection)
                    return
                }
                rejecter(NSError()) //< TODO: unexpected error
            }
    })
    return promise
}

Bolts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public func request<T: ResponseCollectionSerializable>(method: Alamofire.Method, URLString: Alamofire.URLStringConvertible, type: T.Type, parameters: [String: AnyObject]? = nil, encoding: Alamofire.ParameterEncoding = .URL) -> BFTask {
    var task = BFTaskCompletionSource()
    Alamofire.request(method, URLString, parameters: parameters, encoding: encoding)
        .validate()
        .responseCollection { (request, response, collection: [T]?, error) in
            if let error = error {
                task.setError(error)
                return
            }
            if let object = collection {
                task.setResult(object as? AnyObject)
                return
            }
            task.setError(NSError()) //< TODO: unexpected error
        }
    return task.task
}

機能差分

- Genericなインターフェース progress用インターフェース cancel用インターフェース
SwiftTask ⚪︎ ⚪︎ ⚪︎
PromiseKit ⚪︎
Bolts ⚪︎

この表はぼくが気にした点を抽出して書いたものですのでちょっと贔屓目に見えてしまうかも。。。

この他 inamiyさんのこちらの記事 などにも機能差分がまとめられてます。

まとめ

  • どのライブラリも頻繁に更新されパワーアップしているので、どれを使っても大丈夫そうではある
  • 個人的にはタスクを使うほうのインターフェースを一番すっきり書けるSwiftTaskが気に入った
  • また、SwiftTaskには現時点でもprogressやcancel用のインターフェースが用意されているのでこれらが必要になっても困らない
  • SwiftTask++

特にBoltsは本当に表面しか触ってみていませんので、Boltsのほうがココがすげーぞ!といったご指摘は大歓迎です!

Comments