요약
Reactor는 View로부터 Action을 입력받아 Mutation을 생성하고, reduce를 통해 State를 계산하여 View에 제공하는 역할을 한다.
즉, Reactor는 단방향 데이터 흐름(Action -> Mutation -> State)을 강제하는 ViewModel이다.
Reactor의 책임
Reactor는 다음을 책임집니다.
- 입력(Action)을 정의한다.
- View가 발생시키는 사용자 입력을 Action으로 모델링 합니다.
- 상태 변경 단위(Mutation)를 정의한다.
- State를 변경하는 "이유/사건"을 Mutation으로 표현합니다.
- 상태(State)를 정의한다.
- View가 그려야 하는 UI 상태의 단일 진실원천 (SSOT)
- 비동기 작업을 수행한다.
- 네트워크/DB 등 side effect가 존재하는 작업을 수행합니다.
- mutate() 메서드에서 수행
- 해당 작업을 Mutation으로 변환합니다.
- 상태를 계산한다.(reduce).
- Mutation + 기존 State -> 새 State 로 전환
Action
Action은 사용자 인터렉션, 즉 View에서 발생한 입력을 의미합니다.
- UI 이벤트(버튼 탭, 스크롤, 선택 등)
- 생명주기 이벤트(viewDidLoad 등)
- 외부 트리거(갱신 이벤트 등)Example예를 들어 값의 증가와 감소를 카운팅하는 카운터를 만들었다고 해봅시다. 그렇다면 CounterViewReactor는 어떠한 Action을 가질 수 있을까요?
enum Action {
case increase
case decrease
}
위처럼 action에는 값의 증가와 감소 두 가지 action을 할 수 있습니다.
Mutation
Mutation은 Action과 State 사이의 연결 다리입니다. 즉, Mutation은 State를 변경하기 위한 이벤트(상태 변경 단위) 입니다.
- Mutation은 "State를 어떻게 바꿀지"에 대한 최소 단위
- Mutation은 상태 변경 결과 (예: setValue)로 표현하는 편이 테스트에 유리합니다.Example예시의 Counter 앱이 1. 값의 증감 2. 증감 진행중 표시 3. 완료 표시 기능이 필요하다고 해봅시다. 그렇다면 다음과 같은 4가지의 Mutation이 필요할 것입니다.
enum Mutation {
case increaseValue
case decraseValue
case setLoading(Bool)
case setAlertMessage(String)
}
State
State는 View가 그리는 UI 상태의 단일 진실원천(Single Source Of Truth) 입니다.
View에서는 이 State를 바인딩하여 state의 변화에 따라 UI를 새로 그리는 역할을 수행합니다.
- State는 UI 렌더링을 위한 데이터만 가짐
- Side effect 객체는 State에 넣지 않음
- 일회성 이벤트(Alert/Toast)는 @Pulse 사용을 고려ExampleCounter에는 UI에 보여줄 값, 로딩 인디케이터를 위한 로딩 여부의 Bool 값, alertMessage가 필요합니다.
위에서 설명한 일회성 이벤트가 AlertMessage에 해당하므로 @Pulse를 고려할 수 있습니다.
struct State {
var value: Int
var isLoading: Bool
@Pulse var alertMessage: String?
}
데이터 흐름
ReactorKit의 기본 흐름은 다음과 같습니다.
- View가 UI 이벤트를 Action으로 변환해 reactor.action에 바인딩
- Reactor는 mutate(aciton:)에서 Mutation 스트림을 생성
- Reactor는 Mutation을 reduce(state:mutation:)로 누적 계산하여 State 방출
- View는 reactor.state를 구독해 UI 갱신
주요 메서드
1. mutate(action:)
Action을 받아 Mutation 스트림을 생성합니다.
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .increase:
Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.increaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false)),
Observable.just(Mutation.setAlertMessage("Increased!")),
])
case .decrease:
Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.decraseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false)),
Observable.just(Mutation.setAlertMessage("Decreased!")),
])
}
}
- mutate에서 side effect가 수행되어야 합니다.
2. reduce(state:mutation:)
Mutation을 기반으로 새로운 State를 계산합니다.
func reduce(state: State, mutation: Mutation) -> State {
var state = state
switch mutation {
case .increaseValue:
state.value += 1
case .decraseValue:
state.value -= 1
case let .setLoading(isLoading):
state.isLoading = isLoading
case let .setAlertMessage(message):
state.alertMessage = message
}
return state
}
- reduce는 순수 함수(pure funciton) 처럼 유지하는 것이 이상적입니다.
- 네트워크 요청과 같은 side effect는 reduce에 두지 않습니다.
3. transform
ReactorKit에는 transform이 3종류가 있습니다.
func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>
이것들은 "ReactorKit이 기본으로 만드는 스트림 파이프라인" 중간에 끼워 넣을 수 있는 확장 포인트입니다.
- View에서 들어오는 Action 스트림을 가공
- mutate에서 만들어진 Mutation 스트림을 조작
- 최종 State 스트림을 조작/공유할 수 있습니다.
'Swift' 카테고리의 다른 글
| [ReactorKit] ReactorKit 기본 개념 살펴보기 (0) | 2026.01.20 |
|---|---|
| RxSwift - Observable 생성 (0) | 2026.01.14 |
| RxSwift - Observable, RxCocoa, Driver (0) | 2026.01.14 |
| ReactiveX Introduction 번역 (0) | 2025.10.01 |
| [Swift] iOS 앱의 라이프 사이클 관리 (0) | 2025.03.18 |