
기본 개념(Basic Concept)
ReactorKit은 Reactive 및 단방향 플로우 아키텍처를 제공하는 프레임워크 중 하나이다.
아래 사진 처럼 Action과 View State는 Observable을 통해 전달받게 된다.
즉 View는 User Action에 대해 방출만을 할 수 있으며 Reactor는 State에 대해 방출만을 할 수 있다.

ReactorKit을 채택함으로써 얻는 효과는 다음 세 가지가 있다.
- Testability
- Start Small
- Less Typing
이유는 아래와 같다.
- View가 비즈니스 로직과 분리되어 구현
- Reactor는 View와 완전히 독립적인 개체로 존재
- 특정 몇개의 View에게 ReactorKit을 적용 가능 (기존 프로젝트 모든 코드를 다시 작성할 필요X)
- 다른 아키텍처들보다 코드가 간결하고 복잡하지 않다.
View
- iOS에서는 ViewController와 Cell과 같은 것들은 모두 View로 취급
- 비즈니스 로직은 View에 없음
- User Input은 Action Stream에 바인드하고 View States은 UI Component에 바인드
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ProfileViewController: UIViewController, View { | |
var disposeBag = DisposeBag() | |
} | |
profileViewController.reactor = UserViewReactor() // inject reactor |
- View Class에 View라는 프로토콜을 채택하여 구현
- reactor라는 프로퍼티로 접근하여 Reactor를 주입
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func bind(reactor: ProfileViewReactor) { | |
// action (View -> Reactor) | |
refreshButton.rx.tap.map { Reactor.Action.refresh } | |
.bind(to: reactor.action) | |
.disposed(by: self.disposeBag) | |
// state (Reactor -> View) | |
reactor.state.map { $0.isFollowing } | |
.bind(to: followButton.rx.isSelected) | |
.disposed(by: self.disposeBag) | |
} |
- reactor 프로퍼티가 변경되면 bind(reactor:)가 불려진다.
- 위 메서드를 이용하여 Action Stream, State Stream에 접근
Reactor
- UI와 독립적이고 State Stream을 관리
- 모든 View는 하나의 Reactor를 각각 가지고 있음
- Reactor라는 프로토콜을 채택하여 구현
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class ProfileViewReactor: Reactor { | |
// represent user actions | |
enum Action { | |
case refreshFollowingStatus(Int) | |
case follow(Int) | |
} | |
// represent state changes | |
enum Mutation { | |
case setFollowing(Bool) | |
} | |
// represents the current view state | |
struct State { | |
var isFollowing: Bool = false | |
} | |
let initialState: State = State() | |
} |
- Action, Mutation, State를 정의해야함 initialState도 마찬가지
- Mutation은 Action과 State의 중간다리 역할
- Reactor는 Action → State 을 위해 mutate()와 reduce() 두 가지 Step을 거침

mutate()
- mutate()은 Action을 받아서 Observable<Mutation>을 만든다.
- 모든 비동기 처리나 API 요청 같은 Side Effect는 mutate() 메서드에서 로직을 수행한다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func mutate(action: Action) -> Observable<Mutation> | |
func mutate(action: Action) -> Observable<Mutation>; { | |
switch action { | |
case let .refreshFollowingStatus(userID): // receive an action | |
return UserAPI.isFollowing(userID) // create an API stream | |
.map { (isFollowing: Bool) -> Mutation in | |
return Mutation.setFollowing(isFollowing) // convert to Mutation stream | |
} | |
case let .follow(userID): | |
return UserAPI.follow() | |
.map { _ -> Mutation in | |
return Mutation.setFollowing(true) | |
} | |
} | |
} |
reduce()
- reduce()는 이전의 State값과 Mutation을 받아서 새로운 State값으로 업데이트하는 메서드
- reduce()는 pure function이기 떄문에 오직 동기적으로 새로운 State값만을 return한다.
- 이 함수에서는 Side Effect가 발생할만할 로직을 수행해서는 안 된다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func reduce(state: State, mutation: Mutation) -> State | |
func reduce(state: State, mutation: Mutation) -> State { | |
var state = state // create a copy of the old state | |
switch mutation { | |
case let .setFollowing(isFollowing): | |
state.isFollowing = isFollowing // manipulate the state, creating a new state | |
return state // return the new state | |
} | |
} |
transform()
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
func transform(action: Observable<Action>) -> Observable<Action> | |
func transform(mutation: Observable<Mutation>) -> Observable<Mutation> | |
func transform(state: Observable<State>) -> Observable<State> | |
func transform(action: Observable<Action>) -> Observable<Action> { | |
return action.debug("action") // Use RxSwift's debug() operator | |
} |
- transform()은 각각의 Stream을 변환하는 역할을 하고, 총 세 가지의 함수가 있다.
- 이 함수들을 이용하면 다른 Observable Streams들을 변환하거나 병합할 수 있게 된다.
- 예를들어 transform(mutation:)은 글로벌 이벤트 Stream을 mutation stream으로 변환하기 적합하다.