BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース Swiftでネイティブなリアクティブ関数型プログラミングを実現するRxSwift

Swiftでネイティブなリアクティブ関数型プログラミングを実現するRxSwift

ブックマーク

原文(投稿日:2016/02/20)へのリンク

RxSwiftは,RxプログラミングモデルをSwiftに移植することを目的としたプロジェクトで,その抽象化を可能な限り取り入れている。メンテナのKrunoslav Zaher氏に話を聞いた。

RxSwiftでは,Observable<Element>をベースとすることによって,非同期処理やイベントストリームを容易に構成することができる。Observerは,RxSwiftではシーケンスと等価であるため,データやイベントのストリームなどのシーケンスの要素に対する高レベルな操作を,Observableインターフェースのオペレーションを通じてモデル化することが可能だ。

RxSwiftのフレキシブルなプログラミングモデルは,UIを含むバインディングやリトライ,デリゲート,KVO, 通知など,さまざまなユースケースに対応する。

次のプログラム例は,rx_text演算子を使用して2つのテキストフィールドをバインドする方法を示すものだ。一方のフィールドの値が,非同期API(WolframAlphaIsPrime)を通じて,もう一方に関連付けられている。前者の値が変更されると,非同期コールのエンキューによって,後者の値が更新される。

let subscription : Disposable  = primeTextField.rx_text      // Observable<String>型
            .map { WolframAlphaIsPrime($0.toInt() ?? 0) }       // Observable<Observable<Prime>>型
            .concat()                                           // Observable<Prime>型
            .map { "number \($0.n) is prime?\($0.isPrime)" }   // Observable<String>型
            .bindTo(resultLabel.rx_text)                        // 全体をアンバインドするために使用可能なDisposableを返す

// 次の式は,サーバ呼び出しの完了後に, resultLabel.textに
// "number 43 is prime? true"をセットする。
primeTextField.text = "43"

// ...

// すべてアンバインドするには,次のようにすればよい
subscription.dispose()

プロジェクトのメンテナであるKrunoslav Zaher氏に話を聞いた。

Rxでのプログラミングにはどのような発想の転換が必要なのか,説明して頂けますか?

その人のプログラミングに関するバックグラウンドによりますね。ObjectiveCやSwift(そう,Swiftも)での経験がもっぱらだという人たちには,プログラミングではなく,動的システムの挙動を宣言する,という考え方が必要だと思います。

observableシーケンスを使用して,オペレータでそれを変換する場合,実際には,最終的なobservableシーケンスとソースシーケンスの持つ関連をオペレータで宣言することになります。初心者にはそれが奇妙に見えるかも知れませんが,本当に大切なのは,それがobservableシーケンスであると理解することなのです。

observableシーケンスをこれとは違った,正しくない方法で理解すると,さまざまな問題(ストリーム,シグナル,パイプなど)が生じることになります。Rxを考える上で,こういった用語を使用する初心者が少なくないのですが,あまり長い間使っていると,後になっていろいろな面で混乱することになります。こういった用語が使用されるのは,Rxがシステムのステートフルな部分のモデリングでも使用可能なためでもあります。ですが,それはストーリのごく一部ですから,こういった概念について考えない方がよいと思います。

このような(ストリームやシグナル,パイプなど)概念で考えることで問題なのは,そこに暗黙の仮定が存在することにあります:

すなわち,Observableは共有値を伝搬するためのものであり,それが外部から設定可能な現在値のようなものとして存在する,あるいは,Observableは何らかの方法で直接キャンセル可能であり,Future/Promiseと同じように動作する,といったことです。

実際は違います:

Observableはシーケンスの計算方法を定義しているに過ぎません。シーケンスが定義(let result = source.map(transformObservableSequence))されても,計算が自動的に実施されるのではないのです。SwiftSequenceSequence型と同じですね。 計算が実行されるのは,オブザーバが購読(subscribe)される(SequenceTypegenerateメソッドと呼ばれるものと等価)時です。また,結果のDisposableオブジェクトを破棄(dispose)すれば,その計算をキャンセルすることも可能です。 Observableでステートフルな計算を表現して,基盤となるサブスクリプションをソースのobservableシーケンスと(shareReply(1)演算子などで)共有することも可能ですが,これは通常の動作ではありません。

問題の一端は,Future/Promiseや,あるいは同様の動作をする,もっと簡単なリアクティブライブラリが普及していることにあるのかも知れません。そのため,Rxがそれらと同じように動作するものと誤解されて,そのことが事実として初心者を惑わせているのでしょう。

奇妙な話ですが,このような特性がRxを始めた人々にとって大きな問題になり,その最初の障壁のあまりの大さに落胆する人のいる一方で,私自身にとってはこの点こそが,Rxを美しいと思う理由なのです。

美しいというのは,Rxはひとつの文で説明できるからです。Observable<T>が要素Tのobservableシーケンスを表現していて(SwiftSequenceTypeと等価),そのsubscribeメソッド(SequenceTypegenerateメソッドに相当)をコールすることで,シーケンスの要素を受け取る自身のオブザーバ)(コールバック)を登録することができます。

この部分さえクリアになれば,その他の詳細な部分は極めて自明かつ自然なものです。

Rxを独立したコンセプトないし抽象化ではなく,単に新しいライブラリと考えて,Stack Overflowなどのサイトで例題を探すのはよくありません。Rxを学ぶ上で必要なことはただひとつ,observableシーケンスに関する文を理解することだけです。そうなれば他はすぐに納得できるはずです。急勾配の学習曲線も解消します。

Rxを採用するメリットは何ですか?

たくさんありすぎて,すべて紹介できそうもありませんが,

  • プログラミングの必要はありません。プログラムがどう動くべきかを定義して,中間状態や予想外の状況への対処はコンピュータに任せればいいのです。
  • 同じパターンを何度も何度も実装する必要はありません。observableシーケンス上の操作として抽象化すればよいのです。最も一般的なのは,おそらく次のものでしょう。
    • retry
    • combineLatest (Excelなどのスプレッドシートが数式計算を行なう時のように,複数のステートフルオブジェクトの最新値を組み合わせる)
    • map (値のシーケンスを別のシーケンスに変換する)
    • merge (複数のソースからのイベントを組み合わせてひとつのソースにする)
    • flatMapLatest (次の計算要求が到着したとき,前の非同期操作を自動的にキャンセルする)
    • refCount (何かをダウンロードする場合,そのダウンロード結果を少なくとも誰かが必要としていることを確認する。そうであればダウンロード処理を継続するが,誰も必要としないのであればダウンロードを自動的にキャンセルする)
    • zip (N個のネットワーク要求を発行してすべての完了を待ち,結果をmapする,という操作をアウト・オブ・ボックスに実行可能)
  • さまざまな通知や監視の機能をひとつにまとめることができます。例えば,
    • デリゲート
    • 通知センタによる通知
    • KVO
    • ターゲット/アクションパターン
  • Rxを使用することで,処理結果のみならず,処理のキャンセル(破棄)も自由にチェーンすることができます。例えば,
    • イメージのダウンロードを要求する部分がアプリ内に3箇所あって,その内の2つの要求をキャンセルした場合,その情報を必要とする部分が残っているため,ダウンロードは継続されます。
    • 非同期操作をチェーンした複雑なデータ変換が失敗した場合,最終操作を破棄することで,処理全体が自動的に破棄されます。

導入に伴って受け入れる必要のあるデメリットは何かありますか?

パフォーマンス面で大きな影響があるとは思っていません。パフォーマンスには十分に配慮していますから,皆さんが想像しているよりもパフォーマンス上の影響は小さいと言えますし,今後さらに小さくしたいと思っています。

プロジェクト内に簡単なパフォーマンスベンチマークを用意していて,実行時のプロセッサの使用率やメモリアロケーション数のベンチマークに使用しています。これを使えば,個々の操作や構成時のパフォーマンスを簡単にテストできると思います。

個人的に最も問題だと思うのは,コンセプトやライブラリの誤用です。その場合には,デメリットがメリットを上回ることになります。これはコンセプトやライブラリそのものの問題ではありませんが,潜在的コストとして検討の価値はあると思っています。コンセプトの使い方を理解しなかったり,間違った使い方をすることが,実際には最もコストを要するかも知れないのです。

アプリケーションが単方向データフローとして設計されていない場合,そのアーキテクチャを適用せずにRxを導入するだけで,自動的に問題が解決する訳ではありませんし,使い方を十分に理解していない新たなレイヤを導入することで,かえって事態を悪化させてしまう可能性があります。その反対に,RxSwiftの使用例については,ネットワークレイヤやデータモデルの監視など,確立したものがたくさんありますから,メリットを期待できる方法でRxを使い始めることは難しくありません。それがうまくいったら,それを足掛かりにじっくりと進めばよいのです。私たちはこれまで,簡単な適用例をいくつか(RxExampleプロジェクト)提供してこの問題を解決したいと思っていますが,まだまだこれからというところです。

ライブラリの使用時に直面する現実的な問題のひとつは,コールスタックによるデバッグ手法への過度な依存です。リアクティブライブラリの場合,このような方法によるデバッグは適切ではありません。この問題については,システムの状況をトレースするデバッグ用オペレータを,セットとして提供することで解決したいと思っています。

リアクティブアーキテクチャやRxの具体的な問題に関しては,まだやるべきことがたくさん残っています。

RxSwiftの完成度についてはどう思われていますか。実際に使用しているプロジェクトはあるのでしょうか?

私自身,新しいプロジェクトや“銀の弾丸”といったものには懐疑的な方ですので,RxSwiftについても,最初は皆が懐疑的だろうと思っていますし,それは想定の範囲内です。

その一方で私たちは,すべての作業をGitHub上でオープンに行なっています。

私が知っているオープンソースの中で,おそらく最も有名なのはArtsyのEidolonでしょう。エコシステムやGitHub上のRxSwiftCommunityも少しずつ立ち上がってきています。

ダウンロード統計や特定のポッドで使用されているアプリの数は,CocoaPodsで公表されています。RxSwift ∼> 2.0もまずまずの支持を得ることができたので,今後についてはとても楽しみにしています。LinuxのCarthageやSwift Package Managerもサポートしていますが,正確な使用状況を知るために,どれがベストな方法なのかは分かりません。

ロードマップはどうなっていますか?

2.0.0バージョンでは,あまりラディカルな変更はしないつもりです。小機能の追加や,Linuxのサポート改善といったものになるでしょう。

Swift 3.0コンパイラを入手できたら,もっと正確なロードマップを提供できると思います。方向性としては * 型安全性の向上 * パフォーマンス向上 * 省機能 といったものを目指しています。

そう,実は機能を少なくしたいと思っています。できる限り多くをエコシステムのプロジェクトに移行したいのです。RxSwiftライブラリのサイズは現時点が理想的だと思います。新しいオペレータや機能追加への希望が大きいので,このサイズを維持するのはかなり難しいでしょう。

それはつまり,新しいオペレータや拡張機能を取り込み過ぎない方がよいことだと説得するのが難しいのは,火を見るより明らかだからです。このアプローチに皆が不満を持つのは間違いないでしょうが,プロジェクトの保守性を確保するためにはどうしても必要なことなのです。

さまざまな機能をRxSwiftの外部に置くということが,おそらくは今後最も難しい部分になるでしょう。

SwiftでのRxの実装はあなたにとって,どのような経験でしたか?言語の機能はどうだったでしょうか?

Swiftは本当に素晴らしい言語です。私がこれまで経験した中でも,最も美しい言語のひとつであることは間違いありません。その一方で,その有用性を明確に特徴付けるような機能を持ち合わせていないことも事実です。ただし,まだ若い言語ですから,今後そのいったものも実現するに違いありません。

目を凝らして見てみれば,SwiftはC++のクリーンでモダンなバージョンのようにも見えるでしょう。ですが,生成されたコードを見てみると,オーバーヘッドのあまりの大さに驚かされるはずです。その原因のひとつはObjective Cとの相互運用性にありますが,このような問題も解決されていくことを期待しています。

要するにSwiftコンパイラは,まだ子供なのです。それでも,すべてが期待どおりに動作していると思います。

RxSwiftは,[ReactiveX.io]http://reactivex.io/)に可能な限り沿うように考慮されている。ドキュメントはCocoaPods, Carthage, あるいはGitHubから直接インストール可能だ。

 
 

この記事を評価

関連性
形式
 
 

この記事に星をつける

おすすめ度
スタイル

BT