RxSwiftとは?リアクティブプログラミングの基本概念とフレームワーク概要を初心者向けに徹底解説!

目次
- 1 RxSwiftとは?リアクティブプログラミングの基本概念とフレームワーク概要を初心者向けに徹底解説!
- 2 RxSwiftの利点:リアクティブプログラミング導入によるメリットと開発効率向上への効果を徹底解説!
- 3 なぜRxSwiftを使うか?従来の開発手法と比較し、その導入意義と有用性を事例を交えて徹底解説します
- 4 RxSwift入門:インストール・環境構築手順から基本的な使い方まで初心者向けにポイントを交えてサンプルコード付きで徹底解説します
- 5 RxSwiftの実装例・サンプルコード:簡単なプログラムで学ぶリアクティブプログラミングの基礎テクニックを紹介します
- 6 RxSwiftとMVVMアーキテクチャの連携:リアクティブなデータバインディングでUIとモデルを同期する方法
- 7 Observable/Observerの基礎:RxSwiftにおけるデータストリームと購読モデルを理解する
- 8 Subjectの活用方法:RxSwiftにおけるSubjectの種類と手動イベント発行・データ共有のテクニック
- 9 Operator(演算子)の使い方:RxSwiftでデータ変換やフィルタリングを行う演算子の基礎と活用例
RxSwiftとは?リアクティブプログラミングの基本概念とフレームワーク概要を初心者向けに徹底解説!
RxSwiftとは、Swift言語向けに提供されているリアクティブプログラミング用のフレームワークです。リアクティブプログラミングとは、データの変化やイベント発生をストリーム(Observable)として扱い、非同期処理を効率よく組み立てる手法を指します。RxSwiftを使うことで、ユーザーの操作やネットワーク応答など様々なイベントをリアルタイムに監視・加工し、柔軟に対応することができます。
もともとリアクティブプログラミングのライブラリはReactive Extensions (Rx)と呼ばれ、C#で誕生した技術です。その後、多くの言語に移植され広く使われるようになりました。RxSwiftはRxのSwift実装版であり、iOSやmacOS開発において非同期処理やイベント駆動型のコードを書きやすくしてくれます。従来のコールバックやデリゲートを多用するコードに比べ、RxSwiftを導入することで宣言的かつ簡潔なコードで複雑な処理を表現できる点が特徴です。
本節では、RxSwiftの概要と役割、ReactiveXファミリーにおける位置づけ、誕生の背景、リアクティブプログラミングの基本、およびRxSwiftで可能な活用領域について順に解説します。RxSwiftがどのような場面で役立ち、なぜ注目されているのかを理解することで、より効果的にこの強力なライブラリを学ぶことができるでしょう。
RxSwiftの概要と基本的な役割:iOSアプリ開発におけるRxSwiftの位置づけと用途
RxSwiftは、iOSアプリ開発にリアクティブプログラミングの考え方を導入するためのライブラリです。基本的な役割は、ユーザー入力やデータの変化といったイベントをObservable(オブザーバブル)と呼ばれるストリームとして表現し、Observer(オブザーバー)で購読して処理する仕組みを提供することにあります。これにより、時間経過とともに発生する様々なイベントを統一的な方法で扱えるようになります。
従来のImperative(命令的)なコーディングでは、UIのイベント処理にデリゲートやコールバックを使うことが多く、コードの追跡や管理が複雑になりがちです。RxSwiftを使うと、これらのイベントをデータの流れとして宣言的に記述でき、イベント間の関係性やデータの変換処理をチェーン状につなげることができます。その結果、UIイベントの処理や非同期通信のハンドリングをシンプルに記述でき、コードの見通しが良くなるというメリットがあります。
また、RxSwiftは単体のライブラリとして提供されていますが、UI周りの拡張としてRxCocoaというライブラリも用意されています。RxCocoaを利用すると、UIKitの各種UIコンポーネント(UIButtonやUITextFieldなど)をObservable/Observerとして扱えるようになり、UIイベントのバインディングをさらに容易に行えます。これにより、RxSwiftはMVCやMVVMといったアーキテクチャ内でリアクティブな役割を果たし、ビューとデータの同期処理を効率化する用途で広く活用されています。
ReactiveXとRxSwift:Reactive ExtensionsファミリーにおけるSwift実装の特徴
ReactiveX(リアクティブエックス)とは、マイクロソフトによって提唱されたリアクティブプログラミングのためのライブラリ群で、各言語向けにRxと呼ばれる実装が存在します。例えば、.NET向けのRx、Java向けのRxJava、JavaScript向けのRxJSなど様々なバリエーションがあり、共通してObservableや演算子(Operator)などの概念を持っています。RxSwiftはこのReactive Extensionsファミリーの一員で、Swift言語向けに設計された実装です。
ReactiveXファミリーに属するライブラリは、言語は違っても概念やAPIが似ているのが特徴です。RxSwiftでも、他のRx実装と同様にObservable(データの発行元)やObserver(データの購読者)、そして各種演算子(Operators)を用いて非同期処理の流れを組み立てます。ただしSwiftの特徴を活かし、クロージャや強力な型推論機能と相性よく設計されているため、Swiftらしい簡潔な文法でReactive Extensionsの機能を利用できるのが利点です。
RxSwiftの特徴の一つに、膨大な数のオペレーターが提供されている点が挙げられます。ReactiveX由来のmapやfilter、zip、flatMapなどの各種演算子をそのまま利用でき、データストリームの変換や結合、エラー処理まで統一的な文法で記述できます。また、ReactiveXの理念としてプッシュ型の通知モデル(データの発行側が更新をプッシュする)があるため、RxSwiftでも同様にイベント駆動の非同期処理が書きやすくなっています。まとめると、ReactiveXファミリーのSwift版であるRxSwiftは、Reactive Extensionsの汎用性とSwiftのモダンな構文を融合させた強力かつ柔軟なリアクティブライブラリと言えるでしょう。
RxSwift誕生の背景:C#発祥のReactive ExtensionsがSwiftで生まれた経緯
RxSwiftが誕生した背景には、リアクティブプログラミングの有用性がモバイルアプリ開発にも認識されたことがあります。Reactive Extensions(Rx)は2009年頃にMicrosoftのエンジニアによってC#向けに開発され、その後オープンソースとして公開されました。リアクティブプログラミングのパラダイムは、その後数年で各言語・プラットフォームへ急速に広まり、Swiftコミュニティ에서도この考え方を取り入れたいという動きが出てきます。
Swift向けのReactive Extensions実装であるRxSwiftは、オープンソースプロジェクトとして2015年頃に登場しました。iOS開発者たちが中心となってReactiveXのSwift版を作り上げ、GitHub上で公開・メンテナンスされています。Swift言語自体がリリース(2014年)されて間もない時期からRxSwiftプロジェクトが始動したため、当初は試験的な扱いでしたが、Swift言語の成熟とともにRxSwiftも機能拡充と安定化が進みました。
その後、Apple公式からは2019年に同様のリアクティブフレームワークであるCombineが発表されました。しかし、Combineが登場する以前からRxSwiftは広く実務で使われており、現在でも幅広いiOSバージョン(CombineはiOS13+に限定)への対応やReactiveXならではの豊富な演算子群を持つ点で優位性があります。つまり、RxSwiftはコミュニティ主導で育てられ、他言語の知見も取り入れながら成熟してきた経緯があり、その誕生と普及の背景には「非同期処理をもっとスマートに」という開発者の強いニーズがあったのです。
リアクティブプログラミングとは:Observerパターンとの違いとメリットをわかりやすく整理
リアクティブプログラミングとは、データの流れと変化に焦点を当てたプログラミング手法です。これはイベント駆動型のプログラミングを拡張・一般化した考え方で、データやイベントを発生源から購読者へとリアルタイムに伝搬させる仕組みを重視します。典型的な例として、ユーザーの入力やセンサーの値、ネットワークからの応答などを「ストリーム」(流れ)として扱い、そのストリーム上でデータを変換したりフィルタリングしたりしながら最終的な処理を行います。
オブジェクト指向設計でよく使われるObserverパターン(オブザーバーパターン)とリアクティブプログラミングは一見似ています。どちらも「何かの変化を他の部分に通知する」仕組みですが、リアクティブプログラミングでは通知の流れを高度に抽象化・統合して扱える点が異なります。Observerパターンでは通知対象ごとにオブザーバを登録し、イベント発生時にコールバックが呼ばれる形でしたが、RxのリアクティブモデルではすべてのイベントをObservableとして統一的なインターフェースで扱います。さらに、データの変換や結合、フィルタリングといった操作も一連のパイプラインとして記述できるため、複雑な非同期処理でも処理フローを直観的に追いやすくなるメリットがあります。
要するに、リアクティブプログラミングはObserverパターンを発展させた非同期処理の包括的なフレームワークといえます。メリットとしては、コードの見通しの良さ(複数の非同期処理があっても流れが一目でわかる)、モジュール間の疎結合(データの発行側と受け手側を直接結びつけない)、エラー処理の統一(例外もストリーム内で処理可能)などが挙げられます。RxSwiftを使うとこのリアクティブプログラミングの恩恵を受けられ、結果としてアプリの複雑な非同期処理部分をよりエlegant(エレガント)に記述できるようになります。
RxSwiftで何ができるか:UIイベント処理から非同期処理まで幅広い活用領域をカバー
RxSwiftは、UIイベント処理からネットワーク通信、データストア操作まで幅広い場面で活用できます。例えば、ボタンのタップやテキスト入力などのユーザー操作はObservableとして扱えるため、イベント発生を監視して即座に対応するリアクティブUIを構築可能です。複数の入力フィールドの内容をリアルタイムで監視しバリデーションチェックする、といったケースでもRxSwiftなら簡潔に記述できます。
非同期処理においてもRxSwiftは威力を発揮します。ネットワークAPIの呼び出し結果(非同期なHTTPレスポンス)をObservableで受け取り、レスポンスデータを加工してUIに反映する一連の処理を、RxSwiftでは直感的なチェーンで表現できます。複数の非同期処理結果を組み合わせたい場合(例:複数のAPIから取得したデータを統合して画面表示する)にも、zipやcombineLatestといった演算子を使って簡潔に結果を合成できます。
さらに、RxSwiftは並行処理の制御やスレッド管理にも活用できます。デフォルトではObservableの処理はサブスクライブされたスレッドで実行されますが、subscribeOnやobserveOn演算子を用いることで、バックグラウンドスレッドで重い処理を実行しつつ、結果だけメインスレッド(UIスレッド)に渡すといった切り替えも容易です。これにより、UIをブロックせずスムーズなユーザー体験を維持できます。
このようにRxSwiftは、シンプルなUIイベントの取り扱いから複雑な非同期タスクの調整まで幅広い領域をカバーしています。また、アプリケーションのアーキテクチャとして流行しているMVVM(Model-View-ViewModel)との親和性も高く、RxSwiftを使ってViewModelとViewをバインドすることで、ボイラープレートの少ない洗練されたコードを書くことができます。総じて、RxSwiftを導入することでアプリ開発全般における非同期処理の整理・効率化が期待でき、現代的な開発手法として多くのプロジェクトで採用されています。
RxSwiftの利点:リアクティブプログラミング導入によるメリットと開発効率向上への効果を徹底解説!
ここでは、RxSwiftをプロジェクトに導入することで得られる主な利点について解説します。リアクティブプログラミングの考え方を取り入れると、コードの記述方法やアプリの構造に様々なポジティブな効果が現れます。RxSwiftの利点としてよく挙げられるのは、コードの可読性・保守性の向上、非同期処理の簡素化、UIバインディングの効率化、エラー処理の一元化、そしてテスト容易性の向上です。以下でそれぞれのメリットを詳しく見ていきましょう。
従来の手続き型の実装では、非同期処理やイベント処理にコールバックや通知中心の構造を取ることが多く、複数の処理が絡み合うとコードが読みづらくなる傾向がありました。RxSwiftを使えば、そうしたコードを宣言的に書き換えることで理解しやすい形に整理できます。また、コードの見通しが良くなるだけでなく、同じ処理を再利用しやすくなったり、変更に強い柔軟な設計を実現できる点も大きなメリットです。
さらに、RxSwiftはライブラリとして豊富な機能を持ち、UIフレームワークとの統合やスケジューリング、エラー処理、テスト支援まで包括的にサポートしています。これらの機能群を活用することで、開発の効率が上がり、バグの減少やリファクタリングの容易さといった効果も期待できます。以下の各項目で、RxSwift導入による具体的なメリットを順に確認していきましょう。
宣言的コードで可読性・保守性向上:複雑な処理フローをシンプルに整理でき、将来的な修正・拡張も容易になる
RxSwift最大の利点の一つは、コードの可読性と保守性が向上することです。宣言的プログラミングスタイルでコードを書けるため、たとえば「イベントAが発生したらデータXを加工し、結果をUIに反映する」といった処理の流れをコード上で直接表現できます。複数の非同期イベントが絡む場合でも、RxSwiftではそれらを直列・並列にチェーン(連鎖)としてつなぎ、一つの流れとして見通せる形にできます。
具体的には、従来ならネストが深くなりがちだったコールバックの連続(いわゆるコールバック地獄)を、RxSwiftでは演算子を用いてフラットに記述できます。処理の各ステップが明示的な演算子チェーンとして記述されるため、コードを読む人は上から下へ順番に処理内容を追うだけで、全体の流れを把握できます。また、中間結果を変数に保存したりフラグで状態管理するようなボイラープレートコードが減り、ビジネスロジックそのものに集中したコードになるため、後から見返した際の理解が容易です。
このように可読性が上がることで、保守性(メンテナンスのしやすさ)も飛躍的に向上します。新しい要件追加や変更が発生しても、コードの流れが明確で影響範囲を把握しやすいため、修正にかかる手間が減ります。将来的な拡張にも柔軟に対応でき、既存コードに変更を加えても思わぬ副作用が起きにくい構造になりやすいです。結果として、チーム開発においても他のエンジニアがコードを理解・修正しやすくなり、長期的に見た開発効率の向上につながります。
非同期処理の簡素化:コールバック地獄を回避し、直感的なイベント管理を実現して全体像を把握しやすくする
RxSwiftを導入すると、複雑だった非同期処理の実装が格段に簡素化されます。従来は非同期処理を行うたびにデリゲートメソッドやコールバッククロージャを定義し、それらが入れ子状に呼び出されることで処理の順序制御をしていました。この方法だとコードの深さが増し、「いつどこで何が行われているか」を追うのが困難になりがちです。
RxSwiftでは、非同期処理そのものをObservableのストリームとして扱い、後続の処理を演算子でつなげて書くことができます。例えば、ネットワークAPIを呼び出して結果を取得し、その後データベースに保存してからUIに表示する一連の手順を考えてみましょう。RxSwift無しでは、API完了時のコールバック内でDB保存処理を呼び、その完了時のコールバック内でUI更新を行う、といった多重のコールバック構造になりがちです。RxSwiftを使う場合、flatMap演算子で「API取得後にDB保存」を順次実行し、observeOn(MainScheduler.instance)でメインスレッドに切り替えてからUI更新のsubscribeをする、といったふうに一直線のコードで書けます。
このようにコールバック地獄の回避ができるため、非同期処理全体の流れが直感的に理解しやすくなります。処理の全体像をひと目で把握できることは、バグの発見やロジックの見直しにも役立ちます。また、RxSwiftの演算子にはthrottleやdebounceのようにイベント発生頻度を調整するものもあり、高頻度な非同期イベントによる負荷を抑える実装も簡単です。総じて、RxSwiftは非同期処理をシンプルかつ強力に制御する手段を提供し、開発者が意図したとおりの順序・頻度で処理を実現できるようにしてくれます。
UIバインディングの効率化:入力フォームや一覧表示の自動更新を容易にし、開発負担を軽減する
RxSwiftを活用すると、UIとデータのバインディング(結びつけ)を効率よく行えるため、UI更新周りのコードが大幅に簡潔になります。例えば、テキストフィールドの入力内容に応じてボタンの有効/無効を切り替えるようなフォームの検証処理は、通常デリゲートメソッド内で条件チェックをして…といった実装になります。RxSwiftでは、テキストフィールドの文字列をObservableとして監視し、map演算子でその長さや内容に基づきボタンのisEnabled状態に変換し、購読によってUIに適用するだけで完了します。コード量が劇的に減るだけでなく、処理内容が明確なので後から仕様を見直す際にも理解しやすくなります。
このようなUIバインディングの効率化を支えているのが、前述したRxCocoaライブラリの存在です。RxCocoaは、UIKitのUIコンポーネントを拡張して、例えばUIButtonならbutton.rx.tapというObservable(ボタンタップのストリーム)を提供してくれます。開発者はこのストリームに対してsubscribeして処理を記述するだけで、ボタンのタップイベントを取得できます。従来必要だったターゲットアクションの設定やIBOutlet経由での操作も不要になり、イベント処理のためのボイラープレートが削減されます。
リスト表示などのUI更新にもRxSwiftは有用です。たとえば配列データをTableViewに表示する場合、データのObservableに対してbind操作(RxCocoaのbind(to:)など)を行えば、自動的にUIが最新のデータで更新されます。従来はデータ更新時にreloadData()を呼ぶなど手動で同期していた部分が、Rxを導入することでデータとUIの自動同期に置き換わるのです。これらの仕組みにより、UI更新のためのコード量と複雑さが減り、開発者の負担が軽減されます。また、UIとデータの紐付けが明確になることで、不整合や更新漏れといった不具合も起きにくくなるという副次的な効果もあります。
エラー処理の一元化:ストリーム上で例外や失敗を統一的に処理し、エラーハンドリングが簡潔に記述できるようになる
ソフトウェア開発において厄介なエラー処理も、RxSwiftを使うと一元的に扱えるようになります。通常、非同期処理のエラーは各所で個別にハンドリング(do-catchやコールバック内でのerrorチェックなど)しなければなりません。しかし、RxSwiftではエラーも一種のイベント(onError)としてObservableのストリーム上に流すことができます。つまり、正常系のデータ処理とエラー処理を同じフレームワーク内で扱えるのです。
具体的には、Observableは次の三種類のイベントを流す可能性があります: onNext(通常のデータ通知)、onCompleted(完了通知)、onError(エラー発生通知)。RxSwiftでは購読側でonError時の処理をクロージャで渡せるため、各Observableごとにエラー発生時の対処を定めることができます。また、catchError演算子を使えば、エラー発生時に代替のObservableを返す(例えばエラーを示す値を流す、あるいはリトライするなど)処理を挟み込むことも容易です。これにより、分散しがちなエラーハンドリングロジックをストリーム内にまとめ込むことができ、コードの見通しが良くなると同時に抜け漏れのない堅牢なエラー処理が可能となります。
さらに、エラー処理を一元化する利点として、例外処理の統一があります。通常、非同期処理中に発生したエラーはコールバック経由で伝搬し、同期処理中の例外(throw)とは扱いが異なることが多いですが、RxSwiftではどちらもストリーム上でonErrorとして流れるため、開発者は同じ型(Error)として扱えます。結果として、UIへのエラー表示やログ出力などの処理を共通化しやすくなります。例えば、「ネットワークエラーが起きたらトースト表示を行う」といった処理を一箇所にまとめて書けるため、複数箇所で似たようなエラー処理を書く必要がなくなります。このようにRxSwiftはエラーハンドリングを簡潔かつ集中管理できる仕組みを提供し、コード品質と保守性の向上に寄与します。
テスト容易性の向上:非同期ロジックを分離して単体テストを実施しやすくし、不具合の早期発見・解消につながる
RxSwiftの導入は、テストのしやすさにも良い影響を与えます。リアクティブプログラミングを用いると、非同期処理やイベント駆動のロジックをストリームとして分離できるため、ビジネスロジック部分をユニットテストで検証しやすくなるのです。例えば、ボタン押下→API呼び出し→結果を加工→UI更新という流れがあった場合、RxSwiftでは「ボタン押下」という入力Observableから「UIに与える最終結果」のObservableまでを一連のシーケンスで表現できます。このシーケンス部分をテスト環境で実行し、期待した結果が得られるかを確認するだけで、UIを含まないロジックの検証が完結します。
また、RxTestといったテスト支援ライブラリを使うことで、仮想的なスケジューラ上でObservableの挙動を再現し、時間経過をシミュレートしたテストも可能です。これにより、「一定時間内に何回イベントが発生したか」や「エラーが発生した場合の挙動」など、従来はテストしにくかった非同期の振る舞いも検証できます。実際にRxSwiftを利用すると、ビューやコントローラからロジックが分離されるため、ViewModelやモデル部分だけを独立してテストできるようになります。その結果、単体テストで不具合を早期発見しやすくなり、後工程でのバグ混入を減らす効果が期待できます。
さらに、RxSwiftで構築したリアクティブなコードは、副作用が少なく関数型プログラミングに近い性質を持つため、テストだけでなくリファクタリング(コードの改善)も容易です。ある演算子チェーンの途中に処理を追加・削除する場合も、ストリーム全体の入出力が明確なため他への影響を局所化できます。これによりコードの品質を継続的に高めやすく、結果として開発の効率と製品の信頼性向上に繋がります。以上のように、RxSwiftはテストやメンテナンスの観点でもメリットを提供し、堅牢で品質の高いソフトウェア開発を支援してくれます。
なぜRxSwiftを使うか?従来の開発手法と比較し、その導入意義と有用性を事例を交えて徹底解説します
RxSwiftの基本と利点を理解したところで、「そもそもなぜRxSwiftを使う必要があるのか?」という点について考えてみましょう。新しいライブラリやパラダイムを導入する際には、従来手法との比較や、それを使うことによる導入意義を明確にすることが重要です。このセクションでは、従来の手法が抱える課題とRxSwiftによる解決策、具体的な活用シーン、技術トレンドとしての位置づけ、さらには将来性や他技術との互換性まで、包括的にRxSwiftを使う理由を解説します。
従来のiOS開発では、通知と非同期処理にデリゲートやクロージャ(コールバック)を多用してきました。それ自体は有効なパターンですが、アプリの規模や複雑さが増すにつれて管理が難しくなるケースがありました。RxSwiftはそうした課題に対するモダンなソリューションと言えます。以下に具体的な観点ごとに、なぜRxSwiftが有用なのかを見ていきます。
従来手法の限界:コールバックやデリゲート中心の実装が複雑化する問題点を抱える
まず、RxSwift導入の動機となる従来手法の課題について整理します。典型的なiOSアプリ開発では、ユーザーイベントを受け取るためにデリゲートパターン(例:UITableViewDelegate)やターゲットアクション、非同期処理の完了通知にクロージャ(コールバック)を使用することが一般的でした。これらは小規模な範囲では有効に機能しますが、複数の非同期イベントが絡み合う場面では次第にコードが煩雑になります。
例えば、画面Aでボタンをタップするとネットワークリクエストを行い、レスポンスを受け取ったら別の画面Bへ遷移して結果を表示する、という処理を考えてみましょう。従来手法では、ボタンのタップに対するターゲットアクション内でネットワーク処理を呼び出し、その完了ハンドラ内で画面遷移のコードを書く、といった形になります。この段階ではまだ単純ですが、さらにエラーハンドリングや他の非同期処理との連携(例えば並行する別のリクエスト)などが加わると、コールバックの中でコールバックを呼ぶ入れ子構造や、状態を管理するフラグ変数の乱立など、コードが複雑化・冗長化していきます。
加えて、デリゲートメソッドは分散しがちという問題もあります。UI部品ごとに異なるデリゲートが存在し、それぞれ別々の場所で実装されるため、アプリ全体の処理の流れを追うには関連するデリゲート・コールバックを一つ一つ探して辿らなければなりません。結果として、コードの全体像が見えにくい状態になり、バグの原因箇所を特定しづらくなったり、仕様変更時に漏れが生じたりするリスクが高まります。
このように、コールバックやデリゲート中心の従来手法では、規模が大きくなるにつれて管理が難しくなる限界がありました。開発速度の低下やバグ増加といった問題点を抱えることから、それを解決するための新しいアプローチが求められていたのです。その課題に応える形で登場したのがリアクティブプログラミング、そしてRxSwiftでした。
RxSwiftがもたらす解決策:リアクティブプログラミングで非同期処理と状態管理を簡素化する
上述の従来手法の課題に対し、RxSwiftはリアクティブプログラミングのアプローチで解決策を提供します。RxSwiftを導入すると、イベントやデータ変化をすべてObservableの形で扱い、前後する処理を演算子でつなぐことができます。これにより、プログラム内の様々な出来事(ユーザー操作、データ取得、タイマー等)を一貫した方法で購読・処理でき、コード全体を見渡したときの統一感が生まれます。
特に大きな効果があるのが、先述したコールバック地獄の解消です。RxSwiftではflatMapやconcat、combineLatest等の演算子を駆使することで、非同期処理の順序や組み合わせを直感的に記述できます。たとえば、従来は複数のAPI呼び出し結果を待ち合わせるのに、各APIの完了ハンドラ内でカウンタを管理して「両方完了したら次の処理を呼ぶ」としていたものが、RxSwiftならzip演算子一つで二つの結果をまとめて取得できます。
また、状態管理の簡素化も重要なポイントです。リアクティブプログラミングでは、UIの表示状態やデータの一貫性をストリーム上のデータとして捉えるため、画面ごと・コンポーネントごとにフラグを用意して状態遷移を管理するといった必要が減ります。例えば、「ロード中」状態の表示/非表示を示すBool値をBehaviorSubjectで管理し、データ取得処理の前後でonNext(true)/onNext(false)と切り替えるようにしておけば、UI側ではそれを購読してインジケータの表示を切り替えるだけです。従来なら各所でisLoadingフラグを参照していたようなケースでも、RxSwiftならデータと状態の流れを一本化できるため、管理すべきポイントが減ります。
このように、RxSwiftは非同期処理と状態管理をシンプルにし、システムの複雑さを軽減する解決策を提供します。リアクティブプログラミングの考え方によって、開発者は「いつ・どこで・何をするか」をコード上で明示できるようになり、その結果バグの削減や機能追加の容易さといった恩恵を受けることになります。
複数イベントの統合処理:複雑なUIイベントやネットワーク処理をスマートに連携できる
現代のアプリでは、複数のイベントや非同期処理が相互に関連し合う場面が多々あります。例えば「検索バーに文字が入力されて一定時間停止したらサジェストキーワードを取得する」「画面スクロールに応じて追加データをフェッチし、結果を一覧に追加表示する」等、複数の入力やイベントを組み合わせて処理を行うケースです。従来これらを実装しようとすると、タイマーを使ったりフラグ管理したりと複雑なロジックになりがちですが、RxSwiftならスマートにイベントを連携させることができます。
RxSwiftでは、複数のObservable同士を組み合わせるための演算子が豊富に用意されています。例えば、ユーザーのテキスト入力イベント(Observable
また、UIイベント同士の組み合わせも容易です。例えば「ボタンAとボタンBが両方押されて初めて処理を行う」といった場合、2つのボタンのタップObservableをcombineLatestで合成し、両方の値がtrueになったタイミングを捉えることが可能です。従来だとボタンAのハンドラ内でボタンBの状態をチェック…という具合に条件分岐を散りばめていた処理が、RxSwiftでは宣言的な組み合わせとして記述できます。
ネットワークとUI、ユーザー入力とタイマーなど、異なる性質のイベントを統合的に扱えるのもRxSwiftの強みです。全てがObservableの世界に乗るため、種類の違うイベントでも共通の操作でフィルタ・変換・結合ができます。その結果、複雑な同期・非同期処理の連携もコード上で明快になり、高度なユーザー体験(UX)を支える複雑なロジックを堅実に実装できるようになります。このような柔軟性は、RxSwiftを使う大きな動機の一つと言えるでしょう。
技術トレンドとコミュニティ:ReactiveXへの関心拡大と学習資源の充実により導入・習得のハードルが低下
RxSwiftを使う理由には、技術的な利点だけでなくトレンドとしての側面もあります。リアクティブプログラミング(ReactiveX)はここ数年で一気に注目度が高まり、業界全体で関心が拡大しました。それに伴い、インターネット上にはRxSwiftに関する記事・チュートリアル・サンプルコードが豊富に公開されており、公式ドキュメントや書籍も充実してきています。コミュニティも活発で、QiitaやStack Overflowには多数のQ&Aや知見の共有があります。
このように学習資源が整備されてきたことで、以前よりもRxSwiftを習得しやすくなりました。登場直後は「取っつきにくい」「学習コストが高い」と言われたRxSwiftですが、現在では初学者向けの分かりやすい入門記事や実践ノウハウが数多く存在します。また、オープンソースコミュニティによる継続的な改善も行われており、バージョンアップに伴って使いやすさが向上しています。
さらに、リアクティブプログラミングの概念自体が各プラットフォームに広がったことで、他の開発者と共通言語として扱える点も見逃せません。もしチームにリアクティブ思考に慣れた開発者がいれば、RxSwift導入によってスムーズにコミュニケーション・開発を進められるでしょう。つまり、リアクティブプログラミング/RxSwiftは単なる流行ではなく、一種のスタンダードとして定着しつつあり、それを採用することは現代の開発潮流に乗る意味合いも持つのです。
将来性と互換性:AppleのCombineとの比較や他プラットフォーム移植性も視野に入る
最後に、RxSwiftを使うか検討する上で無視できないのが将来性と他技術との関係です。2019年にApple公式からCombineというフレームワークがリリースされ、Swiftにも標準でリアクティブプログラミング手法が提供されるようになりました。また、Swift言語には近年async/await構文(Swift 5.5~)も導入され、非同期処理の表現力が向上しています。これらの登場により、「RxSwiftを今後も使い続けるべきか?」という議論があるのも事実です。
Combineは確かに強力ですが、現時点では対応OSの制約(iOS13以降)や演算子の種類の少なさなどもあり、RxSwiftがすぐ廃れる状況ではありません。むしろRxSwiftで培ったリアクティブ思考のスキルは、そのままCombineや他のリアクティブ実装にも活かせる互換性があります。ReactiveXの考え方自体が各種プラットフォームで共有されているため、一度RxSwiftを習得すればAndroidのRxJavaやWebのRxJSなど他プラットフォームへの知見移植も容易です。
また、現場レベルでは既存プロジェクトにRxSwiftが深く組み込まれているケースも多く、これをCombineへ完全移行するには時間がかかります。そのため、今後数年はRxSwiftとCombineが並行して使われる状況が続くと予想されます。将来的にSwiftの公式サポートがCombineやasync/awaitにシフトしても、RxSwiftコミュニティは柔軟に対応策やブリッジを提供していくでしょう。
要するに、現時点でRxSwiftを使う意義は十分にあると言えます。多くの実績と豊富な機能セットを持つRxSwiftは、今後もリアクティブプログラミングの主要ライブラリとして維持・発展していく可能性が高いです。開発者にとっては、RxSwiftを通じてReactiveXのエコシステムに触れておくこと自体が大きな財産となり、将来の技術選択の幅を広げることにもつながるでしょう。
RxSwift入門:インストール・環境構築手順から基本的な使い方まで初心者向けにポイントを交えてサンプルコード付きで徹底解説します
ここからは、実際にRxSwiftを使い始める方向けに、導入方法や基本的な使い方を解説します。ライブラリのインストール手順や開発環境の設定といった準備から、簡単なObservableの作成と購読、そして実際に手を動かして確認できるサンプルコードまで、初心者がつまずきやすいポイントを交えて丁寧に紹介します。
RxSwiftは強力なライブラリですが、初めて触れる際には「どこから手を付ければよいか」悩むこともあるでしょう。本節では、まずプロジェクトへの組み込み方を説明し、その後RxSwiftの基本中の基本であるObservableの生成とsubscribe(購読)の流れを見ていきます。さらにPlaygroundを使ったお試し方法も紹介しますので、実際にコードを書いてRxSwiftの挙動を体感してみてください。
RxSwiftの導入方法:CocoaPods・Carthage・Swift Package Managerでのインストール手順
まずはRxSwiftをプロジェクトに追加する方法です。RxSwift自体はオープンソースのライブラリであり、Xcodeに標準では含まれていないため、パッケージマネージャ等を用いてプロジェクトに取り込みます。主な導入方法としては以下の3つがあります。
- CocoaPods: 最も一般的なライブラリ管理ツールです。Podfileに
pod 'RxSwift'
および必要に応じてpod 'RxCocoa'
を追加し、pod install
を実行することでインストールできます。 - Carthage: Carthageを使用する場合、Cartfileに
github "ReactiveX/RxSwift" ~> 6.0
のように記述し、carthage update
コマンドでビルドします。生成されたRxSwift.frameworkとRxCocoa.frameworkをXcodeプロジェクトに組み込んでください。 - Swift Package Manager (SPM): XcodeのFileメニューからSwift Packagesを選び、
https://github.com/ReactiveX/RxSwift
リポジトリを追加する方法です。SPMでは依存ライブラリとしてRxSwiftを指定すれば、自動的にプロジェクトに組み込まれます。
現在ではSwift Package Managerが公式にサポートされているため、特に理由が無ければSPMでの導入がおすすめです。いずれの方法でも、インストール後はimport RxSwift
およびUI周りでRxCocoaを使うならimport RxCocoa
を必要なファイルの先頭に記述して利用を開始します。
導入時のポイントとして、RxSwiftのバージョンと自分のプロジェクトのSwiftバージョンやiOSターゲットに互換性があるか確認しましょう。RxSwift 6.xはSwift 5以上が必要など、バージョン間の対応が公式リポジトリに記載されています。また、ビルド後に必要なフレームワークをリンクする(CocoaPodsやSPM利用時は自動設定されますが、Carthage利用時には手動リンクが必要)ことも忘れないようにします。
開発環境の準備:対応するSwiftバージョンとXcode設定のポイント(互換性と動作確認)を詳しく解説
RxSwiftを導入したら、開発環境側での設定や注意点も確認しておきましょう。まずSwiftとRxSwiftのバージョン互換性です。RxSwiftはメジャーバージョンアップの際にSwiftの新機能に追随することが多く、例えばRxSwift 6.x系はSwift 5.3+に対応しています。基本的には最新のSwiftコンパイラで問題なく動作しますが、プロジェクトで古いSwiftを使っている場合は対応するRxSwiftのバージョンを使用する必要があります(公式GitHubのREADMEに対応表が載っています)。
次にXcodeの設定ですが、特別な設定変更は不要です。ただし、CocoaPodsやCarthageで導入した場合は生成されたライブラリ(RxSwift.framework
など)をプロジェクトのリンク設定に追加する工程を確認してください。リンク漏れがあるとビルドエラーになります。SPMの場合は自動でリンクされます。
RxSwiftとRxCocoaを使う際には、ビルドターゲットのiOS Deployment Targetにも注意します。RxCocoaはUIKitに依存するため、iOS以外のターゲット(macOSやwatchOS等)では利用できないAPIもあります。また、RxSwift自体はバックグラウンドスレッドで動作する処理が多いため、デバッグ時にThread SanitizerやMain Thread Checkerの警告が出た場合は、自分のコードでUI更新をメインスレッドで行っているか確認するなど、通常の非同期処理と同様の注意が必要です。
動作確認として、インストール後にまずは簡単なコードを書いてRxSwiftが正しく動くか試してみると良いでしょう。以下のようなコードを実行し、コンソールにログが出力されれば環境構築は成功です。
import RxSwift
let disposeBag = DisposeBag() Observable.of("Hello", "RxSwift", "!") .subscribe(onNext: { print($0) }) .disposed(by: disposeBag)
// 実行結果(コンソール出力): // Hello // RxSwift // !
上記のコードは、文字列の配列からObservableを生成し(Observable.of
)、それを購読して各要素をプリントしています。DisposeBagは後述しますが、購読の管理に使うオブジェクトです。環境構築直後でもこの程度のコードは動かせるはずなので、正しく動作すれば準備完了です。
基本的なObservableの作成:justやfrom演算子でシンプルなストリームを生成する方法
RxSwiftの基本は、Observable(オブザーバブル、観測可能なストリーム)を作成し、それをsubscribe(購読)して利用する流れです。まずは簡単なObservableの作り方を見てみましょう。
もっとも手軽な方法は、Observable.justを使うことです。justは引数に与えた単一の値を即座に流すObservableを返します。一度だけ値を発行して完了するストリームです。
let singleValue = Observable.just("単一の値")
上記コードでは、”単一の値”という文字列を1回だけ流すObservable
let multiValue = Observable.from([1, 2, 3, 4, 5])
from演算子を使うと、配列の各要素を順次nextイベントとして流すObservableが得られます。上記のmultiValue(Observable
さらに、Observable.ofという簡便な初期化方法もあります。Observable.of(値1, 値2, …)と可変長引数で渡すと、その順番で値を流すObservableを生成します。先ほど環境確認で使用したObservable.of("Hello", "RxSwift", "!")
は3つの文字列を順に発行するObservableです。
これらの方法で生成したObservableは、まだデータを流すだけで誰も受け取っていない状態です(購読されるまで動作は開始しません)。次に、生成したObservableからデータを受け取る方法、すなわちsubscribeのやり方を見てみましょう。
subscribeによる購読実行:Observableから値を受け取って処理する基本例を紹介
Observableを利用するには、subscribe(購読)する必要があります。購読とは、Observableから送られてくるイベント(onNext/onError/onCompleted)を受け取って処理を行うことです。RxSwiftでは、Observable型のインスタンスに対してsubscribeメソッドを呼び出し、クロージャでonNext等の処理を定義します。
基本的な例として、先ほど作成したObservable.from([1,2,3,4,5])
を購読して各数値をプリントしてみます。
let numberObservable = Observable.from([1, 2, 3, 4, 5])
numberObservable.subscribe(onNext: { value in print("値: (value)") }, onCompleted: { print("完了しました") })
このコードでは、onNext
クロージャ内で受け取った値をコンソールに表示し、onCompleted
クロージャで完了メッセージを表示しています。実行すると次のような出力が得られます。
// 実行結果例: // 値: 1 // 値: 2 // 値: 3 // 値: 4 // 値: 5 // 完了しました
ポイントは、subscribeを呼んだ時点で初めてObservableが動き出すということです。購読する側が現れなければ、Observableはデータを流しません(ColdなObservableの場合)。上記例でも、subscribeを呼ばなければ何も起こりませんでした。
また、subscribeの戻り値はDisposableというオブジェクトです。Disposableを使うと、後述するように途中で購読を解除(dispose)することができます。手動で解除しなくても、完了(onCompleted)やエラー発生(onError)で自動的に購読は終了しますが、例えばタイマーのObservableなど無限に続くストリームは明示的に解除が必要です。この解除処理にはDisposeBagを用いるのが一般的で、disposed(by: disposeBag)と記述するとそのDisposableはDisposeBagに管理され、DisposeBagがdeinitされるタイミングでまとめて解除されます。
基本的な購読の流れは以上です。実際のアプリでは、onNextだけでなくonErrorでエラー内容をハンドリングしたり、UI更新はメインスレッドで行うようにschedulerを指定したりといった実践的な対応も必要ですが、まずはsubscribeで値を受け取るというシンプルな体験を通して、RxSwiftの動きを掴んでみてください。
Hello RxSwift:Playgroundで手軽に試せる簡単なリアクティブコード実演
RxSwiftの学習を始めたばかりの方は、XcodeのPlaygroundを使ってお手軽にリアクティブコードを試してみるのがおすすめです。Playgroundはコードを書いたそばから実行結果が確認できるインタラクティブな環境で、RxSwiftの動作を理解するのに役立ちます。
まず、CocoaPodsを使ってPlaygroundでRxSwiftを動かすには少し手順が必要です。簡単なのはXcodeプロジェクトにRxSwiftを導入した上で、そのプロジェクトのPlaygroundを作成する方法です。SPMやCocoaPodsでRxSwiftをプロジェクトに組み込んだ状態で、新規Playground(iOS用)を作成し、そのPlaygroundのビルドターゲットに先ほどのプロジェクトのアプリターゲットを指定します。これでPlayground内でもimport RxSwift
等が利用可能になります。
準備ができたら、実際にコードを書いてみましょう。以下は簡単な例です。
import RxSwift import RxCocoa
let disposeBag = DisposeBag()
// 1から5までの値を1秒おきに発行するObservableを作成 let intervalObservable = Observable.interval(.seconds(1), scheduler: MainScheduler.instance) .take(5) // 最初の5つのイベントだけ取り出す
// 購読して値を出力 intervalObservable.subscribe(onNext: { value in print("Tick: (value)") }, onCompleted: { print("タイマー完了") }).disposed(by: disposeBag)
上記コードをPlaygroundで実行すると、1秒ごとにTick: 0, Tick: 1… と順番にコンソールに表示され、最後にタイマー完了と出力されるはずです。Observable
簡単な例でも、値が時間とともに流れていく様子や、take演算子で指定した回数で自動完了する動作など、リアクティブプログラミングの挙動を体験できます。Playgroundで色々と試しつつ、次のセクションでは実際の実装例やサンプルコードを通してさらに理解を深めていきましょう。
RxSwiftの実装例・サンプルコード:簡単なプログラムで学ぶリアクティブプログラミングの基礎テクニックを紹介します
理論を学んだところで、実際にRxSwiftを使った実装例やサンプルコードを見てみましょう。ここでは、基本的なデータストリームの取り扱いから、演算子の活用例、UIイベントの監視、Subjectの利用、そして購読管理の方法まで、代表的なコードパターンを一つずつ紹介します。これらの例を通じて、RxSwiftの具体的な使い方やコードの書き方に慣れていきましょう。
サンプルコードは簡潔さを優先し、本質的な部分のみを抜粋しています。実際のアプリ開発ではエラーハンドリングやUI更新の考慮なども必要ですが、まずはRxSwiftならではのリアクティブな書き方に注目して読み進めてください。
簡単なデータストリームの例:Observableに配列を流して出力する基本コード
最初に、RxSwiftの基本である「データストリームを出力する」コード例を示します。ここでは、整数の配列からObservableを作成し、その値を順次出力する簡単な例です。
import RxSwift
let disposeBag = DisposeBag()
// 1. 配列からObservableを生成 let numbers = Observable.from([10, 20, 30, 40, 50])
// 2. Observableを購読して要素を出力 numbers.subscribe(onNext: { value in print("値: (value)") }, onCompleted: { print("完了") }).disposed(by: disposeBag)
// 実行結果: // 値: 10 // 値: 20 // 値: 30 // 値: 40 // 値: 50 // 完了
上記のコードでは、Observable.from
を使って整数配列[10,20,30,40,50]
からObservable
この例は非常にシンプルですが、「Observableの生成」→「subscribeで利用」というRxSwiftの基本パターンが表れています。配列以外にも、Observable.just(値)で単一の値を流すストリームを作ったり、Observable.empty()で何も流さず即完了するストリームを作ることもできます。用途に応じて様々な生成方法がありますが、まずはfromによる配列→ストリーム変換が基本形として押さえておくとよいでしょう。
map演算子の利用例:数値のObservableを加工して別の値に変換する例
次に、演算子(Operator)を使った例を見てみます。ここでは代表的な演算子であるmapを用いて、数値のObservableの各要素を変換するコードを示します。
import RxSwift
let disposeBag = DisposeBag()
// 数値を2倍に変換するObservable Observable.of(1, 2, 3, 4, 5) .map { $0 * 2 } .subscribe(onNext: { value in print(value) // 値を出力(2,4,6,8,10 が順に出力される) }) .disposed(by: disposeBag)
上記コードでは、Observable.of(1,2,3,4,5)
が生成する1~5の値に対し、.map { $0 * 2 }
を適用しています。これにより、ストリーム上のデータがすべて2倍に変換され、出力される値は2,4,6,8,10となります。map演算子は、入力を別の形にマッピング(対応付け)する基本的な演算子で、非常によく使われます。
この例のように、RxSwiftではObservableに対してドット記法で演算子をつないでいくことで、データの加工やフィルタリング、他のObservableとの結合ができます。例えば、上記コードにさらに.filter { $0 % 4 == 0 }と繋げれば、「2倍にした後4の倍数だけ通す」といったフィルタリングも一行で追加できます。
map以外にも多数の演算子が存在し、それらを組み合わせることで複雑なデータ処理も簡潔に記述可能です。演算子については後述の「Operator(演算子)の使い方」セクションで分類ごとに解説しますが、この例から分かるように、RxSwiftではデータ変換処理が宣言的かつコンパクトに書ける点が魅力です。
UIイベントのObservable例:ボタンタップを監視してログを出力する実装例
ここでは、UIイベント(ユーザー操作)をObservableとして扱う例を紹介します。UIKitと統合するRxCocoaを使い、UIButtonのタップイベントを監視してログを出力するコードを見てみましょう。
import RxSwift import RxCocoa
let disposeBag = DisposeBag()
// 仮のUIButtonを用意 let button = UIButton()
// ボタンのタップイベント(tap)は Observable として取得可能 button.rx.tap .subscribe(onNext: { print("ボタンがタップされました") }) .disposed(by: disposeBag)
上記のコードでは、button.rx.tap
というRxCocoaが提供する拡張プロパティを利用しています。button.rx.tap
はControlEvent
型で、UIButtonがタップされた時にVoid
(中身のない値)をnextイベントとして流すObservableの一種です。このObservableに対してsubscribeし、onNextでログ出力するようにしています。disposed(by: disposeBag)も忘れずに付けています。
この実装により、ユーザーがボタンをタップするたびに「ボタンがタップされました」というログがコンソールに表示されます。従来であればbutton.addTarget(_:action:for:)
やIBActionで実装していた処理ですが、Rxを使うとこのように宣言的にイベントハンドラを記述できるようになります。
さらに発展させて、UIのリアルタイムな反応を実装することも簡単です。例えば二つのボタンのタップを組み合わせて処理する場合、先ほど述べたcombineLatest
やmerge
演算子を使って、複数ボタンのtapイベントを統合し、一箇所でハンドリングできます。RxCocoaを利用することでUIKitの標準UIイベントをObservable化できるため、UIに関するあらゆる操作をリアクティブに処理できる点がRxSwift導入の大きなメリットです。
Subjectを使ったイベント配信:PublishSubjectで複数購読者にイベントをブロードキャストする
RxSwiftには、Subjectと呼ばれる特殊なObservableがあります。Subjectは自らがObserver(イベントの受け手)でもあり、外部から値を受け取って内部でObservableとして配信できるという特性を持ちます。ここでは最も基本的なSubjectであるPublishSubjectを使って、1つのイベントを複数の購読者に通知する例を示します。
import RxSwift
let disposeBag = DisposeBag()
// PublishSubjectの初期化(String型イベントを扱うSubject) let subject = PublishSubject()
// 購読者1を登録 subject.subscribe(onNext: { print("Subscriber1:", $0) }) .disposed(by: disposeBag)
// 購読者2を登録 subject.subscribe(onNext: { print("Subscriber2:", $0) }) .disposed(by: disposeBag)
// Subjectにイベントを発行 subject.onNext("こんにちは") subject.onNext("RxSwift")
このコードでは、subject
というPublishSubjectsubject.onNext("こんにちは")
と"RxSwift"
を呼び出すと、それぞれの購読者(Subscriber1とSubscriber2)のonNextクロージャが呼ばれ、コンソールには次のような出力がなされます。
// 実行結果: // Subscriber1: こんにちは // Subscriber2: こんにちは // Subscriber1: RxSwift // Subscriber2: RxSwift
このように、Subjectを使うと一つのデータソース(発行側)から複数の購読者へ同じイベントを配信できます。まさにイベントのブロードキャスト(Broadcast)を実現する仕組みです。PublishSubject以外にも、最新値をキャッシュして新規購読者に即時配信するBehaviorSubject、過去の複数値をバッファして配信するReplaySubject、完了時の値のみ通知するAsyncSubjectなど種類があります。それぞれ使いどころが異なりますが、基本的な使い方は似ています。
Subjectを利用すると、RxSwift導入前によく行われていたNotificationCenterによるイベント配信や、デリゲートメソッドのdelegate先を複数持てない問題などを解決できます。たとえば、あるデータ更新イベントをアプリ内の複数箇所で聞きたい場合に、NotificationCenterの代わりにSubjectを介して通知する実装も可能です。Subjectは強力な機能ですが、多用しすぎるとコードのトレーサビリティが下がる恐れもあるため、使い所(主に「外部からの入力を内部でObservableに変換するブリッジ」役)が重要になります。
DisposeBagによる購読管理:購読解除とメモリ管理のベストプラクティス
最後に、RxSwift特有の購読管理とメモリリーク防止について触れておきます。RxSwiftではsubscribeによってObservableを購読すると、Disposableという購読ハンドルが返ってきます。これを適切なタイミングで破棄(dispose)しないと、Observableが不要になった後も購読が残り続け、メモリリークや不要な処理の実行につながる恐れがあります。
そのため、RxSwiftではDisposeBagという仕組みを使って購読をまとめて管理するのが一般的です。DisposeBagは簡単に言えば「ゴミ箱」のようなもので、Disposableをdisposed(by: disposeBag)と登録しておくと、DisposeBagが破棄されるときに自動で中の購読を全て解除してくれます。
典型的には、ViewControllerのプロパティにlet disposeBag = DisposeBag()
を用意し、ViewControllerが消える(deinitされる)タイミングでそれに紐づく購読も一斉にdisposeされるようにします。これにより、例えば画面遷移で不要になったObservableの処理がバックグラウンドで走り続けることを防げます。
簡単な例を示します。以下は、定期的に値を出し続けるObservableを作り、一定時間後に購読を解除するコードです。
let disposeBag = DisposeBag()
// 0.5秒毎に0,1,2,...と整数を流すタイマーObservable let timer = Observable.interval(.milliseconds(500), scheduler: MainScheduler.instance)
// 購読して値を出力 timer.subscribe(onNext: { print($0) }) .disposed(by: disposeBag)
// 3秒後にDisposeBagをリセット(破棄)して購読解除 DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) { // DisposeBagを新しく作り直すことで既存の購読をまとめて解除 disposeBag = DisposeBag() }
このコードでは、0.5秒間隔のタイマーObservableを購読し、3秒後にdisposeBagを新しいインスタンスに置き換えることで、元のdisposeBagに入っていた購読を全て解除しています。3秒経過後はタイマーの出力が止まる(購読が解除される)ことを確認できるでしょう。
実際のアプリ開発では、明示的にタイミングを見計らって購読を解除するケースはそれほど多くありません。多くの場合はViewControllerやViewのライフサイクルとDisposeBagを紐付けておき、対象オブジェクトの寿命=購読の寿命とする設計にします。例えば、UITableViewCellごとにDisposeBagを用意し、セルの再利用時にDisposeBagを差し替えて過去の購読を解除する、といったパターンがよく用いられます。
DisposeBagを正しく活用することは、RxSwiftのベストプラクティスです。これによってメモリリークを防ぎ、不要な処理を確実に止めることができます。RxSwiftを導入した際は、各所でdisposed(by: disposeBag)を付け忘れていないか注意し、意図しない挙動がないか確認することが大切です。
RxSwiftとMVVMアーキテクチャの連携:リアクティブなデータバインディングでUIとモデルを同期する方法
RxSwiftは、iOS開発で広く用いられているMVVM (Model-View-ViewModel)アーキテクチャと非常に相性が良いことで知られています。MVVMはUIロジックをViewModelに切り離す設計手法ですが、RxSwiftを組み合わせることでViewとViewModel間のデータ連携をリアクティブに実現できます。このセクションでは、MVVMとは何かを簡単に振り返りつつ、RxSwiftを用いてMVVMパターンを実装する際のポイントを解説します。
RxSwift+MVVMを採用することで、UIイベントとデータ更新が双方向にスムーズに同期され、Viewとモデル層の疎結合をさらに推し進めることが可能になります。具体的にどのように組み合わせるのか、順を追って見ていきましょう。
MVVMとは何か:Model-View-ViewModelで役割を分離し、テストしやすい設計を実現するアーキテクチャ
MVVMは、アプリの構造をModel・View・ViewModelの3つのコンポーネントに分割するアーキテクチャパターンです。各コンポーネントの役割は以下のとおりです。
- Model: アプリのビジネスロジックやデータ管理を担う層。データの取得・保存やドメインロジックを含みます。
- View: ユーザーインターフェース(画面部分)を担当する層。UIKitのUIViewやViewControllerに相当し、画面表示やユーザーからの入力受付を行います。
- ViewModel: ViewとModelの仲介役となる層。Modelから取得したデータを加工してViewが使いやすい形(表示用のデータ)にし、逆にViewからのユーザー入力を受け取ってModelに反映させるなど、双方向の調整を行います。
MVVMの目的は、Viewからビジネスロジックを分離してテストしやすく、かつ再利用可能なコンポーネントを作ることにあります。ViewModelはUIに直接依存しない純粋なロジックの集合体にできるため、単体テストで検証しやすく、また複数のView(例: iPhoneとiPadのUI)で同じViewModelを共有するといった柔軟性も生まれます。
RxSwiftは、このMVVMパターンにおいてViewModelとViewの通信手段として最適です。本来MVVMでは、ViewModelが持つデータをViewが監視し、変化に応じてUIを更新するというデータバインディングの仕組みが重要になります。RxSwift/RxCocoaを使えば、このデータバインディングを簡潔に実装できるのです。次から、その具体的なやり方を見ていきます。
ViewModelにおけるObservableプロパティ:UIに公開するデータをリアクティブに提供する
MVVM+RxSwiftを実現する第一歩は、ViewModelのプロパティをObservable化することです。ViewModelは通常、Viewに表示すべきデータや、ユーザーの操作結果を表す状態などをプロパティとして持っています。これらをRxSwiftのObservableまたはDriver(RxCocoaのUI向けObservableラッパー)として定義することで、View側がそれらを購読し、変化時にUIを更新する仕組みが作れます。
例えば、ログイン画面のViewModelを考えてみます。メールアドレスとパスワードの入力、およびログインボタンの有効可否を扱うとしましょう。
class LoginViewModel { // 入力されたメールアドレス(外部から監視可能) let email: BehaviorSubject = BehaviorSubject(value: "") // 入力されたパスワード(外部から監視可能) let password: BehaviorSubject = BehaviorSubject(value: "") // ログインボタンが有効かどうか(メールとパスワードの内容から計算) var isLoginButtonEnabled: Observable { return Observable.combineLatest(email, password) { email, password in return !email.isEmpty && !password.isEmpty } } // ログイン処理実行結果 let loginResult: PublishSubject = PublishSubject() ... }
このViewModelでは、メールとパスワードはBehaviorSubjectとして状態を保持し、両者の最新値をcombineLatestで監視して、どちらも非空ならログインボタンを有効にするisLoginButtonEnabled Observableを提供しています。View側(UIViewControllerなど)は、ViewModelのこれらObservableプロパティを購読することで、UI要素をバインドできます。
例えば、ViewControllerで
viewModel.isLoginButtonEnabled .bind(to: loginButton.rx.isEnabled) .disposed(by: disposeBag)
と書けば、ログインボタンの有効/無効状態がViewModel側の計算結果と自動で同期します。さらに、メールフィールドの編集結果をViewModelのemail Subjectに送りたい場合、
emailTextField.rx.text.orEmpty .bind(to: viewModel.email) .disposed(by: disposeBag)
とすれば、ユーザーの入力文字列がリアルタイムにViewModel側のemailに流れ込みます。このようにViewModelはObservableなプロパティを通じて、UIに必要なデータや状態をリアクティブに提供します。ViewModel自身は単にデータの流れを定義しているだけなので、UIフレームワークに依存せず、テストも容易なままです。
RxCocoaを用いたUIバインディング:UI側からViewModelのObservableを購読して自動更新する仕組み
上記の例でも触れましたが、RxCocoaを利用することでUIコンポーネントとViewModelのObservableを直接バインドできます。bind(to:)メソッドやrx名前空間のBinderは、UI要素のプロパティ(例えばUIButtonのrx.isEnabledなど)をObservableに結びつける役割を果たします。
もう少し複雑な例として、UITableViewとObservableなデータリストをバインドする場合を考えます。RxCocoaはUITableView用にitemsというデータバインド機能を提供しており、以下のように書くことができます。
viewModel.itemsObservable .bind(to: tableView.rx.items(cellIdentifier: "Cell")) { index, item, cell in cell.textLabel?.text = item.title } .disposed(by: disposeBag)
これによって、ViewModelが提供するitemsObservable(例えばObservable<[Item]>)の中身が変化すると、自動的にテーブルビューのセル表示が更新されます。データソースやデリゲートメソッドを書かずとも、データとUI表示がリンクするわけです。UI側からViewModelのObservableを購読しているとも言え、まさにリアクティブなデータバインディングが実現しています。
また、UI操作をViewModelへ伝える場合も、先ほどのテキストフィールド入力のbindのように、RxCocoaのBinderを通じて送信します。これにより、ViewModel←→View間の依存関係はお互いObservable/Observerという抽象的な繋がりだけになり、コード上でも双方向の反応が直感的に理解できるようになります。
ユーザー入力をSubjectでViewModelへ送信:テキスト入力やボタン操作をリアクティブに処理
ViewModelとViewの連携で重要なのは、ユーザーからの入力イベントをViewModelに伝える仕組みです。前述の通り、テキスト入力はbind(to: viewModel.subject)で実現できますが、ケースによってはViewModel側でPublishSubjectを公開し、それに対してView側からonNextで値を送り込む方法もあります。
例えばViewModel内で
let didTapButton = PublishSubject()
のようなSubjectを用意し、ViewController側で
myButton.rx.tap .bind(to: viewModel.didTapButton) .disposed(by: disposeBag)
とバインドすると、ボタンがタップされるたびにViewModel側のdidTapButton
にイベントが飛ぶようになります。ViewModel側ではそのSubjectをsubscribeするか、さらに別のObservableにflatMapして処理を開始するなどの対応を取れば、ボタンタップに対応するロジックをカプセル化できます。
Subjectは外部からイベントを注入する入口として便利ですが、MVVMでは安易に公開しすぎるとViewModelの内部実装が透けて見えてしまうため、用途を絞るのがポイントです。よくあるパターンは「ユーザーの画面操作開始/終了通知」「リロード要求」など、イベント性質が強く現在値を持たないものをPublishSubjectで受け付けるケースです。一方、現在値を持つものはBehaviorSubjectや普通のObservableプロパティで表現する方が状態管理は楽になります。
いずれにせよ、RxSwift+MVVMではユーザー入力でさえデータストリームとして捉え、ViewModelに流し込む形になります。これにより、ユーザー操作に対する反応も他のデータ更新と対等に扱えるため、例えば「ユーザー操作→即APIコール→結果を別のObservableで返す」などの一連の非同期処理を一括で記述することもできます。従来だとViewControllerでごちゃ混ぜになっていたUI処理とデータ処理が、RxSwift+MVVMでは明確に分離され、しかもリアクティブにつながるため、コード構造が非常に洗練されます。
MVVM+RxSwiftの利点:ViewとViewModelの疎結合化によるUIロジック簡略化とテスト性向上
最後に、RxSwiftを用いてMVVMアーキテクチャを構築することの利点をまとめます。まず第一に、ViewとViewModelの疎結合が一層進む点です。RxSwift導入前でもMVVMはViewModelを介して疎結合を図っていましたが、Rxを使うことでView→ViewModelへの参照すらObservable経由になるため、互いに直接依存しない関係が強化されます。ViewModelはUIフレームワークを知らずに済み、Viewはビジネスロジックの詳細を知らないという理想的な役割分担が実現します。
次に、UIロジックの簡略化があります。RxCocoaのバインディングでUI更新が自動化されるため、ViewController内のコード量が劇的に減ります。従来であれば画面表示のたびにデータをセットしたり、ユーザ操作をデリゲートで受け取ってViewModelに渡したりと、多くのコードが必要でした。それが、bindやsubscribe数行で完結するので、ViewControllerは「画面ライフサイクル管理」と「画面構成」のみに専念でき、非常にスリムになります。
そして、テスト性の向上も大きなメリットです。ViewModelはRxSwiftに依存するとはいえ、UIに依存しない純粋なロジックとして作成できます。さらに、データフローが明示されているため、単体テストでViewModelのObservableにテスト用の値を流し込んで期待通りの出力が得られるか検証する、といったシナリオも容易です。Viewについても、例えばRxCocoaのUIBindingsErrorを利用してバインドミスを検出したり、PublishSubjectを使って模擬ユーザーイベントを発火させUIの反応を見るなど、テスト可能性が高まります。
総じて、MVVM+RxSwiftの組み合わせはモジュールの責務を明確化し、コードの見通しと品質を向上させます。実際、近年のiOSプロジェクトではこのスタイルが一つの定番となっており、リアクティブプログラミングの強みを存分に活かせるアーキテクチャとして広く採用されています。
Observable/Observerの基礎:RxSwiftにおけるデータストリームと購読モデルを理解する
RxSwiftの中心概念であるObservable(オブザーバブル)とObserver(オブザーバー)について、ここで基礎を整理しましょう。これらはReactive Extensionsに共通する考え方で、RxSwiftを理解する上で避けて通れない重要な用語です。ObservableとObserverの関係、イベントの種類、購読の仕組み、さらに少し発展的な話としてコールド/ホットObservableの違いについても解説します。
このセクションを読むことで、「Observableとは結局何なのか」「Observerはどういう役割なのか」といった疑問がクリアになるでしょう。具体例も交えながら説明していきます。
Observableとは:値やイベントのストリームを持ち、複数のObserverにデータ配信する存在
Observable(オブザーバブル)とは、一言で言えば「変化しうる値やイベントの連続(ストリーム)を表現するオブジェクト」です。Observableはデータを保持しつつ、そのデータやイベント(例えば新しい値が入った、エラーが発生した、完了した)を外部に通知することができます。発行元とも言われ、ボタンなら押されたというイベント、ネットワークなら受信したデータ、といった出来事の源泉となります。
Observableは複数のObserver(後述)に対してデータを配信できる点も特徴です。一つのObservableを複数箇所で購読すれば、同じデータストリームを複数の処理で利用できます(ただし、デフォルトでは各Observerごとに独立したデータが配信されます。Hot/Coldの話につながります)。
例として、NotificationCenterをObservableに見立てると分かりやすいかもしれません。NotificationCenterは特定の通知名に対して複数のオブザーバが登録でき、あるとき通知を投稿すると全ての登録先にイベントが飛びます。これをコードではなく型で表現したものがObservableと考えてください。ただし、Observableの場合は様々な便利な演算子でデータ操作ができ、通知タイミングやスレッドも制御できるという大きな違いがあります。
まとめると、RxSwiftにおけるObservableは時間軸上の一連のイベントを格納し、必要に応じて送り出す存在です。その実体はRxSwiftライブラリ内のObservable
Observerとは:Observableから送られるデータを購読して処理を行う受け手側のコンポーネント
Observer(オブザーバー)とは、Observableが発行するイベントを購読し、受け取って処理する側のことです。RxSwiftにおいて特定のクラス名で「Observer」を表すものはありませんが、概念的にはsubscribe処理内のクロージャや、あるいはBinder
(UI側の受け取り口)などがObserverの役割を果たします。
Observerは通常、Observableと同じジェネリック型のデータを扱います。例えばObservable
に対するObserverはInt型の値を受け取る処理を実装することになります。subscribeのonNextクロージャで{ (value:Int) in ... }
と書く部分が、まさにObserverそのものです。
Observerは複数いても構いません。先に説明したように、同じObservableを複数のObserverが購読できます。その際、各Observerは独立してイベントを受け取り処理します(1人が処理し終わるまで次の人に渡らない、などの心配は不要です)。
重要なのは、Observerは能動的にデータを取りに行くのではなく、受動的にデータが来るのを待つという点です。プル型(pull)の反対でプッシュ型(push)とも言われますが、ObserverはObservableがデータを発行するのに合わせて、それを反応的に処理していきます。このため、Observerを実装する開発者側としては、「次に来るデータをどう扱うか」「エラーが来たらどうするか」「完了したらどう後片付けするか」を定義しておけば、あとは流れてくるイベントに応じてそれが繰り返し実行される、というイメージです。
RxSwiftでは、開発者が直接Observerインターフェースを実装することは稀で、subscribeやbindといった高級APIを通じてObserverの役割を果たします。しかし裏ではしっかりObservable→Observerの流れが動いていることを意識することで、リアクティブプログラミングの考え方が腑に落ちるでしょう。
3種類のイベント:onNext・onError・onCompletedの役割と発行タイミング
Observableが送り出すイベントには、基本的に3種類しかありません。このシンプルさがReactive Extensionsの核となる部分です。それぞれのイベントの役割を確認します。
- onNext: 通常のデータ通知イベントです。Observableに新しい値が発生した時に送られます。回数に制限はなく、0回以上、任意のタイミングで何度でも発行できます。
- onError: エラー発生イベントです。Observable内で致命的なエラーが起きた場合に1度だけ送られます。このイベントが送られると、以降そのObservableから他のイベント(onNextやonCompleted)は一切発生しなくなります。
- onCompleted: 完了イベントです。Observableが正常に完了(もうこれ以上値は出ない状態)したことを示します。onErrorと同様に1度だけ送られ、以降他のイベントは発生しません。
重要なルールとして、onErrorかonCompletedのどちらかが発生した時点でObservableのライフサイクルは終了します。両方が発生することはなく、また一度終了したObservableから再度onNextが送られることもありません。Observer側ではこれらイベントを受け取り次第、適切な処理を行い(エラーならエラー処理、完了なら後片付けなど)、通常は購読が終了します。
onNextは複数回送れるため、例えばObservable.from([1,2,3])であれば3回のonNext(値1,2,3)が順に流れ、最後にonCompletedが1回送られる、というシーケンスになります。エラーが起きた場合は途中でonErrorが送られ、まだ残っていたonNext予定やonCompletedは送られずに打ち切られます。
なお、onNextもonErrorもonCompletedも全く送らないObservableというのも概念上は存在します(例えばObservable.never()は何も起こさないストリーム)。また、既定ではObservableのイベント送出は同期的に(subscribeされたタイミングで)行われますが、subscribeOnやobserveOnで非同期に切り替えることができます。このあたりはスレッド制御の話になるので詳細は省きますが、いずれにせよイベントは上記3種類しかないことを覚えておきましょう。
購読の開始と終了:subscribeで購読開始し、disposeで解除する一連の流れ
ここでは、Observableの購読開始から終了までのライフサイクルを改めて整理します。基本的な流れは以下の通りです。
- 購読開始: Observer(購読者)がObservableに対して
subscribe
を呼ぶことで購読が開始されます。ColdなObservableの場合、この時初めて内部の処理が動き出します(Hot Observableなら既に動いている流れに途中から参加する形)。 - イベント受信: 購読が始まると、ObservableからonNextイベントが発生するたびにObserver側のonNextハンドラが呼ばれ、データを受信・処理します。必要に応じてUIを更新したり、別の処理を起動したりします。
- 完了またはエラー: ObservableがonCompletedを発行するか、onErrorを発行した時点で、Observer側ではそれぞれに対応するハンドラ(onCompletedハンドラ、onErrorハンドラ)が呼ばれます。通常これらを受け取ったら購読は自動的に終了します。
- 購読解除: onCompleted/onErrorで終了した場合はそこで購読は終了ですが、開発者が任意のタイミングで購読を止めたい場合は
Disposable.dispose()
を呼ぶことで解除できます。RxSwiftではdisposed(by:)
を使った場合、DisposeBagが破棄されると自動でdisposeされます。
このように、subscribe~disposeまでが一連のライフサイクルです。一度disposeした購読は再開できません。もし再度同じObservableを使いたければ、新たにsubscribeし直す必要があります。
購読解除(dispose)のタイミング管理は、前述のDisposeBagで自動化するのが一般的です。例えばViewControllerが消えるときにその中の購読も全てdisposeされるようにしておけば、リソースリークを防げます。また、例えばネットワークリクエストを途中でキャンセルしたい場合にも、対応するDisposableを保持しておいてdispose()を呼ぶことで、後続のonNextやonCompletedを受け取らずに済みます。
以上が購読の開始から終了までの流れです。この仕組みを理解していると、「なぜsubscribeにはDisposableが返ってくるのか」「DisposeBagは何をしているのか」といった疑問もクリアになるでしょう。RxSwiftはこの購読サイクルを適切にコントロールすることで、効率的かつ安全に非同期処理を扱えるよう設計されています。
コールドObservableとホットObservable:購読タイミングの違いによるデータ配信挙動の変化
最後に、Observableの性質として重要なCold Observable(コールド)とHot Observable(ホット)の違いについて触れておきます。これらはObservableがデータ発行を開始するタイミングや、複数購読者への挙動の違いを示す概念です。
Cold Observableは、購読者が現れたときに初めてデータの発行を開始するタイプのObservableです。典型的なのはObservable.fromやObservable.createで作られるもの、タイマーやネットワークリクエストなど、subscribeしたタイミングから順次データが生成・配信されます。Cold Observableでは、複数のObserverが別々にsubscribeすると、それぞれが独立したデータストリームを受け取ります。例えば、ColdなAPI呼び出しObservableに対して2つのObserverが購読すると、APIリクエスト自体が2回走ることになります(各Observerに対し1セットずつ実行)。
Hot Observableは、Observable自体が既に動き出しており、購読者は途中からそのストリームに参加するようなタイプです。典型例はSubjectやObservable.intervalなどです。HotなObservableでは、Observerが購読した時点で、その瞬間以降に発行されたイベントしか受け取れません(過去のイベントは基本的に流れてこない)。また、複数Observerが購読してもデータ発行源は一つなので、例えばHotなセンサーイベントObservableに複数購読してもセンサー自体は一つしかなく、全員が同じイベントを共有して受け取ります。
簡単に言えば、Coldは「遅れてきた人にも最初からデータを提供する(自分用のストリームを開始する)」、Hotは「遅れてきた人はそれ以降のライブデータだけ受け取る」という違いです。RxSwiftではほとんどの標準ObservableはColdですが、SubjectやConnectableObservable(publish()やshare()を使ったもの)でHotな挙動を作り出せます。
どちらを使うべきかはシナリオ次第ですが、Hotはデータ発行の主体がObservable側にあり、Coldは購読者側にあります。UIイベントや通知などはHot、一回限りの処理(HTTP通信など)はColdであることが多いです。この違いを理解しておくと、RxSwiftで意図通りの動作にならない場合の原因追及に役立ちます。例えば、「なぜ2回subscribeしたら2回API呼び出しが起きるんだろう?」と感じたら、それはCold Observableであるためだと気付けるわけです。
以上、Observable/Observerの基本概念と挙動について解説しました。これらを踏まえてRxSwiftのコードを読むと、内部で何が起きているかがより明確に理解できるでしょう。
Subjectの活用方法:RxSwiftにおけるSubjectの種類と手動イベント発行・データ共有のテクニック
このセクションでは、RxSwiftのSubjectについて詳しく掘り下げます。Subjectは特殊なObservableでありながらObserverとしての一面も持つため、適切に活用することで通常のObservableではできない手動でのイベント発行やブロードキャストが可能です。一方で使い所を誤ると設計が複雑になる側面もあるため、ここでSubjectの種類や特徴、利用パターン、注意点を整理しておきましょう。
RxSwiftには主に4種類のSubject(PublishSubject, BehaviorSubject, ReplaySubject, AsyncSubject)が用意されています。それぞれの特性を理解し、シナリオに応じた使い分けができるようになることが目標です。
Subjectとは:ObserverとObservable双方の性質を持ち、イベント中継に使われるオブジェクト
Subjectは、一種のブリッジ(橋渡し)的なオブジェクトです。RxSwiftのSubjectはObservableでありつつ、自分自身がObserverとして外部からonNext等でイベントを受け取ることができます。平たく言えば、「Subjectに対して値を流し込む(onNextする)と、それを購読しているObserver達にそのまま転送される」という動きをします。
通常のObservableはデータ発行の起点が自身の内部にありますが、Subjectの場合、外部コードから明示的にsubject.onNext(value)と呼ぶことでイベントを発生させられます。そのため、イベントの手動発行が必要な場面でSubjectは重宝します。典型例として、DelegateやCallbackで受け取ったイベントを内部でSubjectに渡し、外部にはObservableとして公開する、という使い方があります。これにより、既存の非RxなコードをRxの世界に変換することが可能になります。
Subject自体はObservable
RxSwiftでSubjectを生成するには、それぞれのクラスのinit()を呼ぶか、便利関数(例: PublishSubject.create())を使用します。なお、Subjectを使わずともObservable.createで似たようなカスタムObservableは作れますが、Subjectの方がシンプルに実装できる場合が多いです。
Subjectの基本利用パターン:onNextでイベント発行し、subscribeで購読する方法
Subjectの基本的な使い方を具体例で確認しましょう。ここでは先ほど示したようなイベント中継の最もシンプルな例を再掲します。
let subject = PublishSubject()
// 購読者の登録 subject.subscribe(onNext: { print("受け取った値:", $0) }) .disposed(by: disposeBag)
// 外部からイベントを発行 subject.onNext("First") subject.onNext("Second")
PublishSubjectを生成し、subscribeで購読者を1つ登録しています。続けてsubjectに対してonNext(“First”)とonNext(“Second”)を呼び出すと、subscribeしたクロージャ内で「受け取った値: First」「受け取った値: Second」がそれぞれプリントされます。購読者が複数いれば、全員に同じ順序でイベントが届きます。
このようにSubjectはonNext
メソッドでイベントを発行し、Observerと同様にsubscribeで購読可能です。Subjectを介することで、普通はObservableを直接返さないような場面でも、柔軟にデータストリームを組み立てられます。
基本パターンとしては、SubjectはonNext以外にもonErrorやonCompletedも呼び出せます。例えばsubject.onCompleted()を呼べば、Subject自体が完了し購読者にはonCompletedが伝わります。エラーも同様です。ただし一度完了またはエラー終了したSubjectに対してそれ以降onNextしても無視されますので注意してください。
実務でSubjectを使う場合、Subject自体は内部に隠蔽し、外部にはObservableとして公開するのが定石です。具体的には、private let subject = PublishSubject
PublishSubjectとBehaviorSubject:過去のイベント有無や初期値の違いによる使い分け
ここからはSubjectの種類ごとの特徴を見ていきます。まずPublishSubjectとBehaviorSubjectの違いです。
PublishSubjectは、最も基本的なSubjectで、過去のイベントを保持しません。購読者が新たにsubscribeしても、それ以前に発行された値は一切受け取れず、購読後に発行されたイベントだけを受信します。言い換えると、常に「今から以降」のイベントを配信するSubjectです。先の例のように、subscribeした後にonNextしたものだけ届くため、シンプルなブロードキャストに向いています。
BehaviorSubjectは、一度発行した最新の値を1つだけ保持するSubjectです。さらに初期値を持つことも特徴です。BehaviorSubjectは購読が開始されると、まず「現在保持している最新の値」を即座に購読者に流します。その後は通常のSubject同様、新しいonNextが来ればそれを流すという動作です。
この違いから、次のような使い分けがなされます。
- PublishSubject: 状態を持たない一過性のイベントに使う(ボタンタップ通知など)。購読時に直前の値は不要で、「これから起こるイベント」をそのまま流したい場合。
- BehaviorSubject: ある状態を表現し、その最新値を常に保持・提供したい場合に使う(フォーム入力値、設定値など)。新規購読者にも直近の状態をすぐ伝えたい場合。
例えば、現在ログインしているかどうかのブール値を扱うならBehaviorSubjectが適しています。BehaviorSubjectをBehaviorSubject(value: false)
のように初期値付きで作っておけば、UI側で購読した瞬間に現在のログイン状態がわかり、それ以降の状態変化も受け取れるというわけです。
一方、ユーザーが画面上でクリックしたという瞬間的なイベントはPublishSubjectで扱うのが自然です。過去にどんなクリックがあったかは通常問わないので、保持の必要もなく、クリックごとにその都度イベントを流せば十分です。
BehaviorSubjectは最後の値をキャッシュするため、必ず何かしらの値を保持しています。そのため、型TがOptionalでない場合は初期値を与える必要があります(上記の例ではfalse
)。Optional型なら初期値無しでBehaviorSubjectを生成できますが、その場合hasValueプロパティで値保持の有無をチェックしながら扱います。
ReplaySubject・AsyncSubjectの特徴:履歴保持や完了時の値取得など特殊な動作を理解する
続いてReplaySubjectとAsyncSubjectです。これらは用途は限定的ですが、特殊な動作を理解しておくと役立つ場面があります。
ReplaySubjectは、過去のイベントをバッファに蓄積し、新規購読者に過去分も再送信できるSubjectです。生成時にバッファサイズを指定し、その数だけ過去イベントを保存します(指定しない場合は全て保存)。新しいObserverが購読すると、保存されていた過去のonNextイベントがすべて(あるいは指定数だけ)順に発行され、その後は通常どおり新しいイベントが配信されます。
例えばlet replaySub = ReplaySubject
とすると、直近2件の値を保持します。先にreplaySub.onNext(1); replaySub.onNext(2); replaySub.onNext(3)
と発行しておき、後から購読したObserverがいれば、「2,3」が最初に送られてから新規イベントが届く形になります。
ReplaySubjectは、過去の履歴を新たな購読者にも見せたい場合に使われます。例えばチャットメッセージの履歴を最後の数件だけ新規参加者に見せる、といった用途や、アプリ起動後に逐次読み込んだログをあとでまとめて処理する場合などに利用できます。ただしイベントを蓄積するので、メモリ使用量や不要な履歴の取り扱いには注意が必要です。
AsyncSubjectは少し変わっていて、Observableの完了時に最後の値を一度だけ通知するSubjectです。つまり、複数値がonNextされても基本は購読者に流さず、onCompletedが呼ばれたタイミングで直前に受け取った値1つだけを購読者に流して完了します。もし完了しなければ何も出さないままです。
AsyncSubjectはあまり一般的ではありませんが、例えば「一連の非同期計算の最終結果だけを他に渡したい」ときなどに使えます。RxSwift以外のReactiveX実装ではFuture/Promise的に使われることもあります。完了を待ってから結果を1回だけ伝えるので、「最終結果の1回」に意味がある処理にマッチします。
まとめると、ReplaySubjectとAsyncSubjectは特殊用途向けです。頻出するのはPublishSubjectとBehaviorSubjectで、これら2つでだいたいのシナリオは足ります。必要に応じてReplayやAsyncを検討すると良いでしょう。
Subjectを使う際の注意点:多重購読時の挙動やメモリ管理で気を付けるべきポイント
最後に、Subject利用時の注意点を述べます。Subjectは便利な反面、乱用するとコードが分かりにくくなったり、思わぬバグを招くことがあります。
まず、Subjectを多用すると「データの流れ」が見えにくくなる恐れがあります。通常のObservableチェーンであれば、上流から下流へデータが流れる経路がコード上で追いやすいですが、Subjectを途中に挟むと、ストリームが一旦切れて別の場所へ飛ぶ形になります。特に複数のSubject間でイベントを飛ばし合うような設計は避けた方が無難です。どこからデータが来てどこへ行くのか把握しづらくなり、デバッグも困難になります。
次に、メモリ管理です。Subject自体はObservableでもあるため、購読が残っている限り参照が維持されます。例えばViewModel内のPublishSubjectをViewController側で購読している場合、ViewControllerが解放されても購読を解除し忘れているとSubjectがViewControllerを強参照してメモリリークを引き起こすことがあります。基本はDisposeBagで適切に管理していれば問題ありませんが、Subjectをグローバルに置いておき画面間で共有するようなケースでは特に注意が必要です。
また、Subjectは競合状態に気を付ける必要もあります。複数のスレッドから同じSubjectに対してonNextを呼ぶような設計は避けるべきです(RxSwift自体は内部でシリアライズされますが、論理的なデータ不整合が起こる可能性があります)。イベントの発行元が複数になる場合は、一箇所に集約して順序を制御するか、適切なScheduler上で実行するよう工夫しましょう。
最後に、前述しましたがSubjectはカプセル化を意識して使うのが鉄則です。外部にSubjectそのものを公開すると、どこからでもonNextされ得る状態になり、バグの温床になります。必ずObservableインターフェースだけ公開して、イベント発行は内側から行うようにして下さい。
以上のポイントに注意すれば、SubjectはRxSwiftの強力なツールとなります。正しく使って、リアクティブプログラミングの幅をさらに広げていきましょう。
Operator(演算子)の使い方:RxSwiftでデータ変換やフィルタリングを行う演算子の基礎と活用例
ReactiveXの真骨頂とも言えるのが各種演算子(Operator)の存在です。演算子を使うことで、Observableが提供するデータストリームに対して変換・フィルタリング・結合・エラー処理など様々な操作を宣言的に適用できます。RxSwiftでも多種多様な演算子が利用可能で、それらを理解すれば複雑な処理もシンプルに記述できるようになります。
本セクションでは、演算子の基本概念と代表的な演算子のカテゴリ別の使い方について解説します。すべての演算子を網羅することはできませんが、よく使われるものを中心に取り上げます。演算子の活用によりRxSwiftの表現力は飛躍的に高まるため、一通り目を通しておきましょう。
演算子(Operator)とは:Observableのデータを変換・加工・フィルタリングするためのメソッド群
まず改めてOperator(演算子)の概念です。ReactiveXにおける演算子とは、Observableに対して呼び出すことで、そのObservableから出てくるデータストリームに何らかの加工を施し、新たなObservableを返すメソッドの総称です。例えばmapも演算子の一つですし、filterやmerge、catchErrorなども全て演算子です。
演算子のイメージは「レゴブロック」によく喩えられます。Observableという土台の上に、mapやfilterといった部品をどんどん積み重ねていくことで、最終的な処理フローを構築します。各演算子は入力Observableを1つ以上受け取り、出力Observableを返すので、それらをメソッドチェーンで繋げることで一連のパイプラインとなります。
ReactiveX公式サイトには豊富な演算子一覧とマーブル図(時系列図)がありますが、RxSwiftでもC#版RxやRxJavaとほぼ同様の演算子が利用可能です。ただし、言語の特性上微妙に使い方が違うものもあります。ここではコード例を交えながら、主要な演算子の使い方を見ていきます。
フィルタリング系Operator:filterやskipなど特定の条件に応じてイベントを間引く演算子
まずはフィルタリング系の演算子です。フィルタリング演算子は、Observableから流れてくるonNextイベント(値)を特定の条件に基づいて通過させたり捨てたりするために使います。
- filter: 条件を満たす要素だけを通過させます。例えば
.filter { $0 % 2 == 0 }
と書けば偶数の値だけが次に進みます。 - skip: 最初のN個の要素をスキップ(無視)します。
.skip(3)
なら最初の3つは捨てて4つ目以降を通します。 - take: 最初のN個の要素だけを取り、残りは無視します。
.take(5)
なら最初の5つだけ通して以降は完了します。 - distinctUntilChanged: 連続して重複する値を排除します。同じ値が続いた場合は一度しか通しません(変化があったときだけ通す)。
- throttle(またはdebounce): 一定時間内に発生した連続イベントを間引きます。UIの連打対策や高頻度イベントの制限に用います。
これらの演算子を組み合わせると、非常に細かなフィルタリングが可能です。例えば「ユーザー入力テキストを300ms待ってから処理し、重複したテキストは無視する」ような場合、
textField.rx.text.orEmpty .debounce(.milliseconds(300), scheduler: MainScheduler.instance) .distinctUntilChanged() .subscribe(onNext: { query in ... })
といったコードになります。この例では入力が300ms停止するごとに最新テキストを取得し、さらに前回と同じ文字列ならスキップするといったフィルタリングをしています。このように、フィルタリング系演算子を駆使すればノイズの多いイベントから必要なものだけを取り出すことが容易になります。
変換系Operator:mapやflatMapでデータ内容を変更し、非同期の結果を結合する
次に変換系の演算子です。変換演算子は、Observableが流すデータそのものを別の形に変換(トランスフォーム)するためのものです。
- map: 先述のとおり、各要素を別の値にマッピングします。例:
.map { $0.name }
でオブジェクトから名前プロパティだけ取り出す。 - flatMap: 各要素に対して新たなObservableを返し、その全てをフラットに1つのObservableに統合します。非同期処理のチェーンに使われます。
- flatMapLatest: flatMapの変種で、最新の要素に対するObservable以外は破棄します。検索クエリに対するAPI呼び出しなど、古いリクエスト結果を無視したい場合に有用です。
- concatMap: 各要素に対するObservableを順序通り直列実行します。複数の非同期タスクを順番に行う場合に使います。
- scan: 初期値から畳み込み計算を行い、途中経過を逐次出力します。例: 数値のObservableにscanを使って累積和を計算する。
変換系演算子の中でも特に重要なのがflatMapです。これは非同期の結果を次のストリームに乗せるための演算子で、RxSwiftで複雑なシーケンスを扱う際のキモになります。たとえば、「ユーザーIDのObservableがあって、それを使ってAPI呼び出しし、その結果(JSON)をパースして表示する」という流れは、
userIdObservable .flatMap { id in fetchUserInfoAPI(id: id) } // Observableを返す .map { $0.name } .bind(to: nameLabel.rx.text) .disposed(by: disposeBag)
のように書けます。fetchUserInfoAPIは引数のIDでAPIリクエストしObservable
もしflatMapを使わずにやろうとすると、subscribeの中で別のObservableをsubscribeするネスト構造になってしまいます。flatMapのおかげでネストせずにストリームを連結できている点に注目してください。
このように変換系演算子は、データ内容の加工だけでなく、ストリーム同士をつなぐ役割も果たします。flatMap/concatMapあたりは最初は取っ付きにくいですが、理解するとRxSwiftの表現力が格段に上がるので、ぜひマスターしましょう。
組み合わせ系Operator:merge・zip・combineLatestで複数Observableを統合して処理する
続いて組み合わせ系の演算子です。これは複数のObservableを統合して一つのObservableにまとめるタイプの演算子です。
- merge: 複数のObservableを結合し、届いた順序で交互にイベントを流します。複数のストリームを一つにまとめたい場合に使用。
- zip: 複数Observableのそれぞれの要素をタプルにまとめて出力します。全Observableから要素が揃うごとに発行されます。
- combineLatest: 複数Observableの最新の要素同士を組み合わせて出力します。いずれかのObservableに新しい要素が来ると、他の最新値と合わせて発行します。
- withLatestFrom: 片方のObservableの新イベントに合わせて、もう片方の最新値を取り出して組にする演算子です。
- startWith: あるObservableの先頭に指定した値を差し込みます。初期値の発行に使います。
組み合わせ演算子を使うと、異なるデータ源をまとめて処理することができます。例えば、二つのテキストフィールドの入力内容を結合して検索クエリにする場合、combineLatestを使って
Observable.combineLatest(firstNameField.rx.text.orEmpty, lastNameField.rx.text.orEmpty) { first, last in return "(first) (last)" } .subscribe(onNext: { fullName in print("名前: (fullName)") })
と書けば、いずれかのフィールドが更新される度に最新の姓+名を組み合わせた文字列が出力されます。これは従来のデリゲートではなかなか面倒な処理ですが、combineLatestなら簡潔です。
zipは例えば複数のAPIから得た結果を1対1で組みにして処理したい場合に有用です。複数ストリームの同期を取りたいときに使います。一方mergeは複数のUIイベントストリームを一本化する場合などに使えます(A画面/B画面それぞれの”保存”ボタンのイベントを一つの処理に流し込む、等)。
これら演算子はいずれもObservableを多引数取り、各演算子ごとに定められたタイミングでデータを発行するものです。組み合わせ系を駆使すると、並行する非同期処理の合流地点を簡単に作れるため、プログラム全体の同期・調停ロジックが見通し良く書けるようになります。
エラー処理系Operator:catchErrorやretryでエラー時に代替処理や再試行を行う
最後にエラー処理系の演算子です。これはObservable内でエラー(onErrorイベント)が発生した際の振る舞いを制御するための演算子です。
- catchError: エラーが発生した際に、別のObservableに切り替えることができます。例えばエラー時にデフォルト値を返すObservableに置き換える等。
- retry: エラー発生時に、購読を一定回数再試行します。
.retry(3)
なら最大3回まで再度subscribeし直します。 - retryWhen: エラー時にカスタムのトリガー(Observable)を待って再試行することもできます。
- materialize/dematerialize: 上級者向けですが、エラーも含めてイベントを値として扱い、ハンドリングするためのものです。
基本的によく使われるのはcatchErrorとretryです。たとえば、ネットワークAPIのObservableに対して
apiObservable .retry(2) // 最大2回再試行 .catchError { error in // エラー時は空の結果を返してストリームを正常終了させる print("エラー発生:", error) return Observable.just([]) }
のように書けば、APIが一時的に失敗した場合は2回までリトライし、それでもダメならエラー内容をログ出力しつつ空の配列を次のストリームに流して処理を続行…といったことが可能になります。
エラー処理系演算子を駆使すると、先述の通りエラーハンドリングをストリーム内に組み込めるため、コードの分岐が減り読みやすくなります。ただし、むやみにエラーを握りつぶすと不具合を見逃す可能性もあるため、適切なログ出力やユーザーへの通知とのバランスを考えて使う必要があります。
以上、主要な演算子のカテゴリーごとに使い方を説明しました。ここで挙げた以外にも、遅延実行のためのdelay、順序を入れ替えるscan、条件分岐のtakeUntilなど、多数の演算子が存在します。初めは戸惑うかもしれませんが、RxSwiftで何か実装するとき「この処理は演算子で書けないか?」と考えてみると、だんだんと適切な演算子が思い浮かぶようになります。演算子を使いこなせれば、RxSwiftのコードは驚くほど簡潔で意図が明確なものとなるでしょう。