ReactorKit의 기본적인 구조에 대해서 기록하고자 한다.
최근 했던 프로젝트에서 ReactorKit을 이용하여 구성했다.
ReactorKit은 단방향 데이터 플로우를 가지고 리액터를 이용하여 뷰에서의 액션을 처리하고 데이터를 수정한다.
해당 데이터의 bind 처리를 하여 사용자에게 변화를 보여주는 방식이다.
위 그림과 같이 뷰에서 액션을 리액터에 보내고 리액터는 해당 액션에 맞게 State의 프로퍼티를 수정하며 뷰에 나타낸다.
ReactorKit을 이용하면서 ViewController의 코드를 줄이고 조금 더 애자일스러운 구조를 갖게 된다.
간단한 코드도 함께 기록해보자.
간단하게 api 통신을 통해 List를 불러오는 코드이다.
func bind(reactor: CustomReactor) {
onButton.rx.tap.bind(with: self) { owner, item in
reactor.action.onNext(.loading(true))
}.disposed(by: disposeBag)
offButton.rx.tap.bind(with: self) { owner, item in
reactor.action.onNext(.loading(false))
}.disposed(by: disposeBag)
reactor.state.map { $0.isLoading }
.distinctUntilChanged()
.bind(to: activityIndicatorView.rx.isAnimating)
.disposed(by: disposeBag)
}
위 코드는 viewController에서 View를 상속했을 때 오버라이딩 되는 bind함수 내부다.
bind에서 ReactorKit의 State 데이터의 변경에 맞게 UI를 변경하도록 구현했다.
final class CustomReactor: Reactor {
enum Action {
case loading(_ on: Bool)
}
enum Mutation {
case setLoading(Bool)
}
struct State {
var isLoading: Bool = false
}
let initialState: State
init() {
self.initialState = State()
}
}
Action은 View에서 Observable<>타입으로 방출한다.
사용자의 입력, UI 변경에 따른 액션 구분이 필요하다.
Mutation은 Action과 State의 매개체 역할을 하고, Action에서 받은 타입을 니즈에 맞게 변환하여 reduce()로 전달한다.
State는 해당 뷰에서 필요한 프로퍼티들을 저장한다. 해당 state 값에 따라 UI가 변경된다.
extension CustomReactor {
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .loading(let isOn)
return .just(.setLoading(isOn))
}
}
func reduce(state: State, mutation: Mutation) -> State {
var newState = state
switch mutation {
case .setLoading(let isLoading):
newState.isLoading = isLoading
return newState
}
}
}
mutate 에서 reduce()에서 사용할 수 있도록 Action을 Observable<Mutation>
타입으로 변환하여 반환한다.
reduce() 에서는 mutation 에서 받은 데이터를 이용하여 state를 변경하고 반환한다.