RxSwiftは、非同期の値やUIイベントを「流れ(ストリーム)」として扱うReactiveXのSwift実装です。ただし2026年に入ってから導入まわりの前提が変わりました。6.10.0でCocoaPodsサポートが正式に非推奨となり、CocoaPods本体のtrunkも2026年12月2日に読み取り専用へ移行する予定です。この記事では、6.10.2を実際にSwift Package Managerで取り込み、コードを走らせて確認した挙動をもとに、RxSwiftの基本概念と現在の導入手順、CombineやSwift Concurrencyとの選び分けを整理します。
まとめ:RxSwiftの要点
- 最新版は6.10.2(2026年3月2日公開)。リポジトリは更新が続いており、Xcode 26・Swift 6.2に対応済みです。
- 導入はSwift Package Managerが既定。6.10.0の破壊的変更でCocoaPodsサポートは非推奨になり、READMEからもPodfileの記載が消えました。主な選択肢はSPM・Carthage・配布用XCFrameworkで、手動ビルドやgit submoduleも案内されています。
- 基本は3つ――値を流す
Observable、購読するsubscribe、購読を束ねて破棄するDisposeBag。ここまで理解すれば入門としては足ります。 - RxSwift 6で演算子名が変わっています。
catchErrorはcatch、retryWhenはretry(when:)、observeOnはobserve(on:)です。古い記事のコードをそのまま貼ると名前が合いません。 - 新規採用はCombineやSwift Concurrencyが第一候補。RxSwiftが優位なのは、既存のRxコードベース、UIKitの細かいバインディング、RxTestによる時間軸のテスト、Linux/Androidを含む非Darwin環境です。
RxSwiftの正体:ReactiveXのSwift実装と2026年の開発状況
ReactiveXは、C#のReactive Extensions(Rx.NET)を出発点に、RxJava・RxJS・RxSwiftなど各言語へ移植されてきた仕様群です。RxSwiftはそのSwift版で、「値やイベントの列」という一つの抽象で、配列の要素・ネットワーク応答・ボタンのタップ・KVOの変化を同じ書き方に揃えます。個別のコールバックやデリゲートを、mapやfilterといった演算子でつなぎ替えられるのが利点です。
GitHubのReactiveX/RxSwiftは直近のコミットが2026年9月4日で、開発は続いています。公式のリリース一覧で確認できる最新リリースは6.10.2(2026年3月2日)で、6.10系のリリースノートには「Xcode 26 & Swift 6.2 compatibility」「Android/non-Darwin support in Package.swift with CI testing」「Swift Concurrency enabled for Linux」が並びます。6.10.2自体は6.10.1との差がMac Catalyst対応の修正とSwift 5.9向けマニフェストの調整だけで、機能面は6.10.0と同じです。
配布されるライブラリは1つではなく、用途ごとに分かれています。textField.rx.textなどの標準UI拡張を使うにはRxCocoaも必要なので、最初に構成を押さえておくと迷いません。
| モジュール | 役割 | 代表的な型 |
|---|---|---|
| RxSwift | コア。ストリームと演算子 | Observable / Single / Infallible |
| RxCocoa | UIKit・AppKitとの接続 | Driver / Signal / ControlProperty |
| RxRelay | エラーで終わらないSubject相当 | PublishRelay / BehaviorRelay |
| RxTest | 仮想時間でのテスト | TestScheduler |
| RxBlocking | テスト用の同期待ち | BlockingObservable |
対応プラットフォームは、Swift 5.9以降のツールチェーンが読む[email protected]でiOS 12・macOS 10.13・watchOS 4・tvOS 12・visionOS 1と宣言されています。RxCocoaはUIKitやAppKitに依存するため、Linuxなど非Darwin環境ではコア側のみが対象です。なお、パッケージ自身の言語モードはswiftLanguageVersions: [.v5]のままですが、これは利用側の制約ではありません。Swift 6言語モードを有効にしたターゲットからRxSwift 6.10.2を使うコードは、Swift 6.3.3でそのままビルドできました。
RxSwiftの導入:SPMの設定とCocoaPodsの非推奨化
旧来の入門記事はまずCocoaPodsを案内していましたが、この前提はもう使えません。6.10.0のリリースノートは破壊的変更の先頭に「CocoaPods support has been officially deprecated.」と書き、移行先としてSwift Package ManagerかCarthageを指しています。背景はCocoaPods側の縮退で、公式ブログ「CocoaPods Trunk Read-only Plan」によれば、2026年11月1〜7日にテスト運用、2026年12月2日にtrunkが新しいPodspecを恒久的に受け付けなくなります。既存のビルドは動き続けますが、以後はtrunk経由の新バージョンが流れてきません。
現在のREADMEに残る導入方法は、手動ビルド・XCFramework・Carthage・Swift Package Manager・git submoduleの5つです。新規プロジェクトはSPMを選びます。Xcodeから追加する場合はFile > Add Package DependenciesでリポジトリURLを指定し、パッケージ形式なら次のように書きます。
// swift-tools-version:5.9
import PackageDescription
let package = Package(
name: "MyApp",
platforms: [.iOS(.v15)],
dependencies: [
.package(url: "https://github.com/ReactiveX/RxSwift.git", .upToNextMajor(from: "6.10.2"))
],
targets: [
.target(name: "MyApp", dependencies: [
.product(name: "RxSwift", package: "RxSwift"),
.product(name: "RxCocoa", package: "RxSwift")
])
]
)
注意点が2つあります。1つは、READMEがSPMの節で現在も警告しているクロス依存の不具合(SR-12303)です。RxSwiftに依存する別パッケージを同時に解決すると重複リンクで失敗する場合があり、回避できないときはXCFrameworkの直接組み込みが現実的な逃げ道になります。もう1つはRxTestやRxBlockingをテストで使う場合で、テストターゲットの依存に必要なプロダクトを追加して実行します。READMEにはTEST=1 swift testという案内が残っていますが、6.10.2のマニフェストはこの環境変数を参照せず、RxTestとRxBlockingを常に公開しています。バイナリで入れる場合、各リリースに添付されるXCFrameworkはShai Mishali名義のApple Developerアカウントで署名されており、Xcodeのフレームワーク設定から署名チームを確認できます。SPM全般の書き方とCocoaPodsからの移行手順はSwift Package Manager入門|使い方とPackage.swift、CocoaPods移行で詳しく扱っています。
Observable・subscribe・DisposeBag:最小コードと実際の出力
RxSwiftのコードは、作る(Observable)→つなぐ(演算子)→購読する(subscribe)→捨てる(dispose)の4段で読むと迷いません。以下はSwift 6.3.3とRxSwift 6.10.2で実行した結果です。
import RxSwift
let bag = DisposeBag()
Observable.from([1, 2, 3, 4])
.filter { $0 % 2 == 0 }
.map { $0 * 10 }
.subscribe(onNext: { print("next:", $0) },
onCompleted: { print("completed") })
.disposed(by: bag)
// 実行結果
// next: 20
// next: 40
// completed
subscribeが返すDisposableをdisposed(by:)でDisposeBagに預けておくと、バッグを持つオブジェクト(多くはViewControllerやViewModel)が解放された時点で購読がまとめて解除されます。有限のストリームは完了やエラーで終端した時点で解放されますが、UIイベントのように終端しない購読は、DisposeBagか明示的なdispose()で解除しない限り残り続けます。さらに、DisposeBagへ預けても、クロージャが所有者のselfを強参照していれば循環参照は残るため、[weak self]やwithUnretained(_:)を併用します。実際にスコープを抜けてバッグを破棄すると、その後に流した値は購読側へ届きません。
let subject = PublishSubject<Int>()
do {
let localBag = DisposeBag()
subject.subscribe(onNext: { print("inScope:", $0) }).disposed(by: localBag)
subject.onNext(1) // inScope: 1
}
subject.onNext(2) // localBag が解放済みのため何も出力されない
もう一つ押さえるべきは、Observableにはホットとコールドの両方があり、生成方法や共有方法で性質が変わる点です。Observable.createに渡した処理は購読のたびに最初から走ります。実測では、同じObservableを2回subscribeするとファクトリのクロージャが2回実行されました。APIリクエストを内側に書いたObservableを複数箇所で購読すると、通信も購読の数だけ発生します。同じ接続期間の購読で処理と直近の値を共有したいときはshare(replay: 1)を使います。ただし既定のスコープは.whileConnectedなので、完了後や購読者がいなくなった後の再購読では処理が再実行されます。もう1点、6.10.0で入った回帰として、既定のshare()に2つのスレッドから同時に最初の購読を行うとクラッシュする不具合が報告されています(Issue #2718)。修正は2026年8月13日にmainへ取り込まれましたが、2026年9月20日時点でこれを含むリリースはまだ出ていません。
SubjectとRelay:購読時の再送と終端イベントの違い
Subjectは、外部から手動で値を流せるObservableです。種類の違いは「購読より前に流れた値を受け取れるか」に集約されます。
let ps = PublishSubject<String>()
ps.subscribe(onNext: { print("A:", $0) }).disposed(by: bag)
ps.onNext("before-B")
ps.subscribe(onNext: { print("B:", $0) }).disposed(by: bag)
ps.onNext("after-B")
// 実行結果
// A: before-B
// A: after-B
// B: after-B // B は購読前の値を受け取らない
let bs = BehaviorSubject<Int>(value: 0)
bs.onNext(1)
bs.subscribe(onNext: { print("BS:", $0) }).disposed(by: bag)
// 実行結果
// BS: 1 // 直近の1件だけ再送される
画面の状態のように「常に現在値がある」ものはBehaviorSubject、ボタンタップのような一過性の通知はPublishSubject、直近n件を配りたいならReplaySubjectという判断になります。
UI用途で実際に多く使われるのはRxRelayのRelayです。SubjectはonErrorやonCompletedでストリームが終端し、以降は何も流れなくなります。画面の状態管理でこれが起きると復旧手段がないため、エラーと完了を発行できないRelayが選ばれます。BehaviorRelayはaccept(_:)で値を送り、valueで現在値を同期的に読めます(実測では初期値100・accept(200)後にvalueが200)。ViewModelの出力をBehaviorRelayで保持し、Viewがそれを購読する形がMVVMでの定番です。アーキテクチャ側の役割分担はMVCとは?MVVM・MVPとの違いと使い分けを参照してください。
演算子:合流のしかたと、RxSwift 6で変わった名前
演算子は数が多いものの、入門段階で必要なのは変換(map/flatMap)、絞り込み(filter/distinctUntilChanged/debounce)、合流(merge/combineLatest/zip)、エラー処理(catch/retry)の4系統です。混同しやすいのは合流の2つで、挙動の差は実行するとはっきりします。
let a = PublishSubject<Int>(), b = PublishSubject<String>()
Observable.combineLatest(a, b) { "\($0)\($1)" }
.subscribe(onNext: { print("combineLatest:", $0) }).disposed(by: bag)
Observable.zip(a, b) { "\($0)\($1)" }
.subscribe(onNext: { print("zip:", $0) }).disposed(by: bag)
a.onNext(1); b.onNext("x"); a.onNext(2); b.onNext("y")
// 実行結果
// combineLatest: 1x
// zip: 1x
// combineLatest: 2x // 片方が更新されるたびに最新同士を再計算
// combineLatest: 2y
// zip: 2y // 互いの n 番目同士だけをペアにする
入力フォームの検証のように「どれか1つが変わったら全体を再評価したい」ならcombineLatest、2つの結果を1対1で対応付けたいならzipです。エラー処理側では、catchが代替のObservableへ切り替え、retry(_:)は初回を含む最大試行回数を指定します。retry(3)なら初回と最大2回の再購読です。実測では、2回失敗して3回目に成功するObservableにretry(3)を付けると3回目の値が流れました。
ここで古い記事との食い違いが出ます。RxSwift 6のリリースノートは多数のリネームを明記しており、5系の名前で書かれたサンプルはそのままでは今の名前と一致しません。
| RxSwift 5までの名前 | RxSwift 6以降の名前 |
|---|---|
| catchError(_:) | catch(_:) |
| catchErrorJustReturn(_:) | catchAndReturn(_:) |
| retryWhen(_:) | retry(when:) |
| observeOn / subscribeOn | observe(on:) / subscribe(on:) |
| takeUntil(_:) / takeWhile(_:) | take(until:) / take(while:) |
| skipWhile(_:) | skip(while:) |
| elementAt(_:) | element(at:) |
旧名は削除されたわけではなく、@available(*, deprecated, renamed:)を付けた別名として残っています。Swift 6.3.3で旧名のコードをビルドしたところ、7つすべてが「renamed to …」の警告付きで通りました。移行は一度に全置換しなくても、警告を手がかりに順次進められます。
あわせてRxSwift 6では、失敗しないことを型で保証するInfallible、購読中に対象を弱参照で受け取るwithUnretained(_:)、Dataを直接デコードするdecode(type:decoder:)が追加され、SingleEventがResult<Element, Swift.Error>に置き換わってsubscribe(onSuccess:onFailure:)という形になりました。
RxSwiftとSwift Concurrencyの相互運用:valuesとasObservableによる変換
RxSwiftは6.5以降、Swift Concurrencyとの相互変換を標準で持っています。ObservableはvaluesでAsyncSequenceとしてfor try awaitでき、Infallible・Driver・Signalはエラーを出さないためfor awaitで回せます。単発のSingle・Maybe・Completableはtry await single.valueのように値を直接待てます(値を出さずに完了したMaybeはnil)。
for try await value in Observable.from([7, 8, 9]).values {
print(value) // 7, 8, 9
}
逆向きはAsyncStreamなどにasObservable()を呼ぶだけです。ここは6.10.0で挙動が変わった箇所で、内部の反復がTaskからTask.detachedになりました。呼び出し元のアクターコンテキストを意図せず引き継いでデッドロックする事故を避けるための変更で、あわせてasObservable(priority: .userInitiated)のように優先度を渡せるようになっています。既存コードがメインアクター上での実行を暗黙に期待していた場合は、明示的にobserve(on: MainScheduler.instance)を挟む必要があります。
この相互変換があるため、移行は全面書き換えでなく境界単位で進められます。新しく書く層はasync/awaitにして、既存のRxのストリームとはvaluesとasObservable()で接続するのが現実的です。async/await自体の考え方は非同期処理とは?同期処理との違いから実装方式までで整理しています。
RxSwift・Combine・Swift Concurrencyの選び分け
2026年時点で新規プロジェクトにRxSwiftを入れる理由は、以前より限定的です。Appleは標準でCombineとSwift Concurrencyを提供しており、追加依存なしで非同期処理を書けます。一方でRxSwiftが引き続き有利な場面もはっきりしています。
| 観点 | RxSwift 6.10 | Combine | Swift Concurrency |
|---|---|---|---|
| 提供元 | OSS(外部依存) | Apple標準 | Swift言語標準 |
| 最低OS | iOS 12 / macOS 10.13 | iOS 13 / macOS 10.15 | iOS 13以降へ後方展開 |
| 非Darwin環境 | Linux・Androidに対応 | 非対応 | Linux・Windowsに対応 |
| UIKitバインディング | RxCocoaのrx拡張 | UI接続を個別実装 | UI専用バインディングAPIなし |
| 時間軸のテスト | RxTestのTestScheduler | テスト用Schedulerを別途用意 | Clockによる時間源の抽象化 |
| 他言語との共通知識 | RxJava・RxJSと共通 | Apple固有 | 言語機能 |
判断は単純です。UIKitで組んだ既存アプリにRxのコードが残っているなら、無理に剥がさずRxSwiftを使い続ける。RxCocoaのバインディングやRxTestの仮想時間テストに広く依存している場合は、その再実装と回帰テストの費用を移行判断に含めます。一方、SwiftUIで新規に書く画面はCombineとSwift Concurrencyです。RxCocoaはUIKit/AppKitを前提にしているため、SwiftUIのビューに直接つなぐ道具が揃っていません。Combine側の仕組みと採用判断はCombine(Swift)とは?Publisher・Subscriberの仕組みと2026年の採用判断にまとめています。
移行を検討する場合、名前が似ていても意味がずれる箇所に注意が必要です。RxのObservableにはホットとコールドの両方があり、CombineのPublisherはプロトコルであり挙動は実装次第で、UIから使う@PublishedやCurrentValueSubjectはBehaviorRelayに近い振る舞いをします。また、Combineは失敗型(Failure)を型パラメータで持つため、RxSwiftでError一本だったエラー処理は型の設計から見直しになります。スケジューラではobserve(on:)とreceive(on:)がどちらも下流へのイベント配送先を指定しますが、名前が対応していないため置き換え箇所の洗い出しが要ります。
よくある質問
RxSwiftの最新バージョンは?
2026年9月20日時点の最新リリースは6.10.2で、公開日は2026年3月2日です。6.10.1との差はMac Catalyst対応の修正とSwift 5.9向けマニフェストの調整で、機能は6.10.0と同一です。リリースはGitHubのReactiveX/RxSwiftで公開されています。
CocoaPodsでRxSwiftを入れられますか?
既存のPodfileは当面動きますが、新規採用は避けてください。RxSwift 6.10.0でCocoaPodsサポートは正式に非推奨となり、公式READMEからも導入手順が削除されました。CocoaPodsのtrunk自体も2026年12月2日に読み取り専用へ切り替わる予定です。移行先はSwift Package Manager(推奨)かCarthageです。
RxSwiftとCombineはどちらを選ぶべきですか?
UIKitベースの既存アプリでRxのコードが動いているならRxSwiftの継続、SwiftUIでの新規開発ならCombineとSwift Concurrencyが基準になります。RxSwiftを選ぶ具体的な理由になるのは、RxCocoaのバインディング、RxTestによる仮想時間テスト、LinuxやAndroidを含む非Darwin環境、RxJava・RxJSと共通の知識資産です。
catchErrorやobserveOnはまだ使えますか?
6.10.2時点では非推奨の別名として残っており、ビルド自体は通ります。Swift 6.3.3で実際にコンパイルするとcatchError is deprecated: renamed to catch(_:)のような警告が出るだけでエラーにはなりませんでした。ただし新しい名前(catch(_:)/catchAndReturn(_:)/retry(when:)/observe(on:)/subscribe(on:))への置き換えが前提なので、新規コードでは旧名を使わないでください。
import RxSwiftだけでUIのバインディングは書けますか?
独自のバインディングはRxSwiftのBinderやsubscribeで実装できます。ただし、textField.rx.textやbutton.rx.tapといったUIKit拡張はRxCocoaが提供しており、import RxCocoaが別途必要です。BehaviorRelayなどのRelay型もRxRelayの提供ですが、RxCocoaを取り込むと依存として一緒に入ります。SwiftPMではRxSwiftパッケージの中の別プロダクトとして指定します。