TCA(The Composable Architecture)の記事では単方向データフローを扱いました。実は韓国のiOS界隈には、それより前に単方向を標準のように定着させたフレームワークがあります。それがReactorKitです。
全秀烈(Suyeol Jeon)氏が2017年に公開したこのフレームワークは、StyleShareやカカオ系列のサービスが導入したことで、UIKit + RxSwiftの事実上の標準アーキテクチャになりました。求人広告に「ReactorKit経験者優遇」が頻繁に登場した時期さえあります。
今回はReactorKitの構造、TCAとの違い、そして現在の位置付けを整理します。
ViewとReactor、役割は2つだけ
ReactorKitの構造はシンプルです。1つの画面をViewとReactorに分けます。
- View:ユーザー入力を
Actionへ流し、Stateを購読して画面を描画します。ロジックは持ちません。 - Reactor:Actionを受け取り、Stateを生成するロジックの塊です。UIのことは一切知りません。
重要なのは、Reactor内部のデータフローが一方向に固定されている点です。
Action → mutate() → Mutation → reduce() → State → View
ボタンタップがAction.refreshとして入ると、mutate()がネットワーク要求などの副作用を処理し、Mutation.setItems([...])を発行します。reduce()はこのMutationを受け取り、新しいStateを計算します。ViewはStateを購読し、画面を更新します。
Mutationという中間段階はなぜ必要なのか
MVVM(Model-View-ViewModel)に慣れていると、「Actionから直接Stateを変えればよいのでは?」と疑問に思うかもしれません。Mutationを挟むのは、非同期処理を隔離するためです。
mutate()は非同期処理が許される唯一の場所です。一方、reduce()は純粋関数なので、同じStateとMutationを渡せば常に同じ結果になります。副作用が1か所に固定されるため、状態がなぜ変わったのか追跡するときに見る場所も1か所です。
final class SearchReactor: Reactor {
enum Action { case updateQuery(String) }
enum Mutation { case setResults([Repo]) }
struct State { var results: [Repo] = [] }
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .updateQuery(let query):
return service.search(query)
.map { Mutation.setResults($0) }
}
}
func reduce(state: State, mutation: Mutation) -> State {
var newState = state
switch mutation {
case .setResults(let repos): newState.results = repos
}
return newState
}
}
ReactorはUIを知らない純粋なロジックなので、テストも簡単です。Actionを与えてStateを確認すれば終わりです。
TCAとは何が違うのか
どちらもReduxの影響を受けた単方向アーキテクチャですが、適用単位が異なります。
| ReactorKit | TCA | |
|---|---|---|
| 基盤 | RxSwift | Combine·Swift Concurrency |
| 適用単位 | 画面(View)ごとにReactorを1つ | 機能単位のReducerをツリー状に合成 |
| 導入方法 | 画面ごとに段階的に導入可能 | アプリ全体を1つの体系として構成 |
| 主な対象 | UIKit | SwiftUI |
ReactorKitの実用性は導入方法にあります。既存のMVC(Model-View-Controller)プロジェクトでも、1つの画面を選んでReactorを追加すれば動作します。アプリ全体を書き換える必要はありません。一方TCAは、機能間の状態合成までフレームワークが管理する代わりに、体系全体を受け入れる必要があります。
ちなみにReduxをiOSへ直接移植したReSwiftというライブラリもありましたが、アプリ全体で単一ストアを使う方式はUIKitとの相性が悪く、ReactorKitほど広まりませんでした。
現在のReactorKit
正直なところ、新規プロジェクトでReactorKitを選ぶケースは減っています。弱点が明確になったためです。
1つ目は、RxSwiftに強く結合していることです。AppleのエコシステムがCombineとSwift Concurrencyへ移行する中、RxSwift依存そのものが負担になりました。
2つ目は、SwiftUIと相性がよくないことです。Viewプロトコルとバインディング方式が、UIKitの命令型更新を前提に設計されています。
それでもReactorKitが残した功績は明らかです。「状態は一方向にだけ流れる」「副作用は1か所に隔離する」という感覚を、韓国のiOS開発者にいち早く根付かせたのがこのフレームワークです。今TCAを学んでいる人なら、Action・Mutation・Stateの構造に見覚えがあるでしょう。その感覚の多くはReactorKit時代に形作られました。
既存のUIKit + RxSwiftコードベースを保守しているなら、ReactorKitは今も実績のある選択肢です。画面単位で追加・削除できる構造なので、後から画面ごとに取り除いて移行するのも比較的容易です。
まとめ
- ReactorKitは、1つの画面をViewとReactorに分け、Action → Mutation → Stateという単方向フローを強制するRxSwiftベースのアーキテクチャです。
- 非同期処理は
mutate()に、純粋な状態計算はreduce()に隔離されます。そのため追跡とテストが容易です。 - TCAと概念は同じですが、画面単位で段階的に導入できる実用性が差別化ポイントです。
- RxSwiftとの結合やSwiftUI非対応のため新規導入は減りましたが、UIKitのレガシーコードでは今も有効です。
次回は、画面ではなくビジネスロジック単位でアプリを分割するUberのRIBsを扱います。VIPERの記事で一段落だけ触れた、あのアーキテクチャを詳しく掘り下げます。

![[iOSアーキテクチャ #9] ReactorKitまとめ、単方向アーキテクチャの標準のカバー画像](/assets/images/posts/317cfd0d-2a59-49f9-9bce-a85ec25ae593/reactorkit-unidirectional-flow-1.jpg)