In the TCA (The Composable Architecture) article, we covered unidirectional data flow. In Korea’s iOS scene, however, one framework had already made unidirectional architecture feel standard: ReactorKit.
Released by Suyeol Jeon in 2017, this framework became the de facto architecture for UIKit + RxSwift as StyleShare and Kakao-affiliated services adopted it. There was even a time when “ReactorKit experience preferred” commonly appeared in job postings.
Today, we’ll examine ReactorKit’s structure, how it differs from TCA, and where it stands now.
View and Reactor: There Are Exactly Two Roles
ReactorKit’s structure is simple. Each screen is split into a View and a Reactor.
- View: It sends user input to
Actionand subscribes toStateto render the screen. It contains no logic. - Reactor: A block of logic that receives Actions and produces State. It knows nothing about the UI.
The key is that data flow inside the Reactor is fixed in one direction.
Action → mutate() → Mutation → reduce() → State → View
When a button tap enters Action.refresh, mutate() handles side effects such as network requests and emits Mutation.setItems([...]). reduce() receives that Mutation and calculates a new State. The View subscribes to the State and updates the screen.
Why Is Mutation Needed as an Intermediate Step?
If you know MVVM (Model-View-ViewModel), you may wonder, “Why not change State directly from the Action?” Mutation exists to isolate asynchronous work.
mutate() is the only place where asynchronous work is allowed. By contrast, reduce() is a pure function: the same State and Mutation always produce the same result. Since side effects are confined to one place, there is only one place to inspect when tracing why state changed.
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
}
}
Because the Reactor is pure logic that knows nothing about the UI, testing is simple. Send in an Action and inspect the State.
How Is It Different from TCA?
Both are unidirectional architectures influenced by Redux, but they differ in their unit of application.
| ReactorKit | TCA | |
|---|---|---|
| Foundation | RxSwift | Combine·Swift Concurrency |
| Unit of application | One Reactor per screen (View) | Compose feature-level Reducers into a tree |
| Adoption approach | Can be introduced incrementally, one screen at a time | Organize the entire app as one system |
| Primary setting | UIKit | SwiftUI |
ReactorKit’s practicality comes from its adoption model. In an existing MVC (Model-View-Controller) project, you can attach a Reactor to just one screen and start using it. There is no need to overhaul the entire app. TCA, on the other hand, manages state composition across features but requires adopting the system as a whole.
There was also a library called ReSwift, a direct iOS port of Redux. However, its app-wide single-store model was difficult to reconcile with UIKit, so it never spread as widely as ReactorKit.
ReactorKit Today
Honestly, fewer new projects are choosing ReactorKit because its weaknesses have become clearer.
First, it is tightly coupled to RxSwift. As Apple’s ecosystem moved toward Combine and Swift Concurrency, the RxSwift dependency itself became a burden.
Second, it does not fit SwiftUI. Its View protocol and binding model were designed around UIKit’s imperative updates.
Still, ReactorKit’s legacy is clear. It was the first framework to instill in Korean iOS developers the intuition that “state flows in one direction” and “side effects belong in one isolated place.” If you are learning TCA today, the Action–Mutation–State structure probably feels familiar; much of that intuition was formed during the ReactorKit era.
If you maintain an existing UIKit + RxSwift codebase, ReactorKit remains a proven choice. Because it attaches to and detaches from screens independently, migrating later by removing it screen by screen is relatively manageable.
Summary
- ReactorKit is an RxSwift-based architecture that splits each screen into a View and a Reactor and enforces the unidirectional Action → Mutation → State flow.
- Asynchronous work is isolated in
mutate(), and pure state computation inreduce(). That makes tracing and testing easy. - Its concepts are the same as TCA’s, but its practical differentiator is incremental, screen-by-screen adoption.
- New adoption has declined because of its RxSwift coupling and incompatibility with SwiftUI, but it remains valid in UIKit legacy codebases.
The next article covers Uber’s RIBs, which splits an app by business-logic units rather than screens. We’ll take a proper look at the architecture mentioned only in one paragraph of the VIPER article.

![Cover image for [iOS Architecture #9] ReactorKit: The Unidirectional Standard](/assets/images/posts/317cfd0d-2a59-49f9-9bce-a85ec25ae593/reactorkit-unidirectional-flow-1.jpg)