RxSwiftをやめてReactiveKitに乗り換えた話

###### 2016/4/8 追記しました

個人で開発しているアプリ「Limentor」にて、ベース部分にRxSwiftを使っていたのですが、つい最近になってReactiveKitに乗り換えました。

RxSwiftは、Swift界隈では言わずと知れたライブラリで、MVVMパターン(のようなもの)を簡単に実装できます。似たようなライブラリでSwiftBondやReactKitなどがあります。

アプリを開発し始めた当初は、プロパティのバインディングが1行で実装できるSwiftBondを使っていたのですが、APIリクエストのフローを1回でやりたいなと思い、イベントの種類が存在するRxSwiftに移行したんですよね。

移行したのはいいがバインディングが面倒くさかった。。。

そこで登場したのが「ReactiveKit」です(ReactKitではありませんよ)。
ReactiveKitは、「SwiftBond」を生み出したsrdanrasicさんが新しく開発したライブラリで、ベースにはSwiftBondを使っているようです。

このエントリでは、ReactiveKitの導入方法&簡単な使い方とRxSwiftから移行した感想を紹介したいと思います。

ReactiveKitの導入

CocoaPodsで導入する場合はPodfileに以下を記述して「pod install」

pod 'ReactiveKit', '~> 1.0'
pod 'ReactiveUIKit', '~> 1.0'
pod 'ReactiveFoundation', '~> 1.0'

Carthageで導入する場合はCartfileに以下を記述して「carthage update –platform iOS」

github "ReactiveKit/ReactiveKit" 
github "ReactiveKit/ReactiveUIKit"
github "ReactiveKit/ReactiveFoundation"

ReactiveKitの使い方

ReactiveKitはベースがSwiftBondと全く同じです。例えば、値のバインディングは次のように実装できます。

textField.rText.bindTo(viewModel.text).disposeIn(disposeBag)

上記例では、ReactiveUIKitをインポートすることで使用可能になるUITextFieldのrTextプロパティを、viewModelのtextプロパティにバインドしています。bindToメソッドに渡せるプロパティはプロトコルやwhereによって幾つか条件が指定されているのですが、Observableなプロパティならば指定可能なので1行でバインドできます。

disposeBagプロパティは、RxSwiftでもおなじみのdisposableを格納するバッグのことです。disposeBagが解放されたタイミングでバインドを解除してくれるため使うことが推奨されていると思います。

private var disposeBag = DisposeBag()

こんな感じでインスタンスプロパティとして宣言しておけば良いのではないでしょうか。バインドをすべて解除したい場合は、

disposeBag = DisposeBag()

とします。といっても、ViewControllerが破棄されるタイミングでも勝手にdisposeBagも解放されるので、意図的にバインドを解除したい場合以外は、特に気にする必要はないと思います。

ちなみにViewModelに記述されているtextプロパティは

private(set) var text: Observable<String?> = Observable(nil)

このように、Observable<String?>という型で定義しておく必要があります。

次にOperationを紹介します。Operationは・・・なんでしょうね。RxSwiftのObservableのようなものと言ったらいいでしょうか。使い方は次のような感じです(READMEより)。

func fetchImage(url: NSURL) -> Operation<UIImage, NSError> {
  return Operation { observer in
    let request = Alamofire.request(.GET, url: url).response { request, response, data, error in
      if let error = error {
        observer.failure(error)
      } else {
        observer.next(UIImage(imageWithData: data!))
        observer.success()
      }
    }
    return BlockDisposable {
      request.cancel()
    }
  }
}

こ・・・これは、RxSwiftと同じような書き方ができる!しかもエラーの型を指定することができるので、呼び出し元でエラーの型を判別する必要がありません!非常に便利ですね。 SwiftBondでは値のバインディングは簡単にできたのですが、イベントのエラーハンドリングなどができなかった(イベント状態が1つしか存在しなかった。おそらくnextのみ。)のでOperationの導入は非常に画期的だと思いました。

ちなみに、Limentorでは、OperationをAPIリクエストの呼び出しに使っています。

extension Requestable where ResponseType.DecodedType == ResponseType{
    func response() -> Operation<ResponseType, ApiError>{
        return Operation{observer in
            let request = API.sendRequest(self){result in
                switch result{
                case .Success(let response):
                    observer.next(response)
                    observer.success()
                case .Failure(let error):
                    observer.failure(error)
                }
            }

            return BlockDisposable{
                request.cancel()
            }
        }
    }
}

こんな感じです(詳細はgithubのコード見てください)。

RxSwiftから移行して

バインドが非常に楽になりました。今まではViewModelのプロパティに値を代入するためだけにsubscribeして非常に面倒くさかったですね。さらにViewでViewModelの値代入処理を書くのでViewModelのプロパティをinternalにしないといけませんでした・・・。が、ReactiveKitを使えばバインドは1行でできますし、SwiftBondではできなかったイベントのエラー判定もOperationによって可能になりました。

とりあえず、今後はReactiveKitで実装していくと思います。というかそろそろ本格的に作り始めないとまずい。来年度までには作れるといいなぁ。

追記
2016/4/8

どうやらRxSwiftの「2.0.0-beta.4」あたりからVariable がbindToの引数に指定できるようになっていました! ということでRxSwiftに戻しています。。。いったい何をやっているんでしょうね。