이번에 ReactorKit GitHub의 Example에 있는 Counter를 직접 구현해보면서 ReactorKit에 대해 공부해보았다.
'단방향 데이터 flow를 가진 반응형 앱을 위한 프레임워크'로 Flux + Reactive Programming이다.
Flux란?
👉 Facebook에서 클라이언트-사이드 웹 어플리케이션을 만들기 위해 사용하는 애플리케이션 아키텍쳐다. 단방향 데이터 흐름을 활용해 뷰 컴포넌트를 구성하는 React를 보완하는 역할을 한다.
import UIKit
import ReactorKit
import RxSwift
import RxCocoa
final class CounterViewController: UIViewController, ReactorKit.StoryboardView {
// UI (생략)
var disposeBag: DisposeBag()
func bind(reactor: CounterViewReactor) {
// Action
// decreaseButton / increaseButton 버튼 tap시, Action 발생
decreaseButton.rx.tap // Tap event
.map { Reactor.Action.decrease } // Convert to Action.decrease
.bind(to: reactor.action) // Bind to reactor.action
.disposed(by: self.disposeBag)
increaseButton.rx.tap
.map { Reactor.Action.increase }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
// State
reactor.state
.map { $0.value }
.distinctUntilChanged()
.map { String(stringLiteral: "\($0)") }
.bind(to: valueLabel.rx.text) // // Bind to valueLabel
.disposed(by: self.disposeBag)
reactor.state
.map { $0.isLoading }
.distinctUntilChanged()
.bind(to: indicator.rx.isAnimating)
.disposed(by: self.disposeBag)
}
}
View에서 전달받은 Action에 따라 business logic 수행
상태를 관리하고 상태가 변경되면 View에 전달
대부분의 View는 대응되는 Reactor를 가짐
import Foundation
import ReactorKit
import RxSwift
final class CounterViewReactor: Reactor {
// MARK: - View의 Action 정의
enum Action { // 추상화 된 사용자 입력
// 사용자 입력..
case increase
case decrease
}
// MARK: - Action을 받을 Mutation 정의
enum Mutation {
case increaseValue
case decreaseValue
case setLoading(Bool)
}
// MARK: - View의 현재 상태
struct State { // 추상화 된 뷰 상태
// 뷰 상태..
var value: Int
var isLoading: Bool
}
var initialState: State
init() {
self.initialState = State(value: 0, isLoading: false)
}
// MARK: - Action -> Mutation
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .increase:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.increaseValue)
.delay(.seconds(1), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
case .decrease:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.decreaseValue)
.delay(.seconds(1), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
}
}
// MARK: - Mutation -> State
func reduce(state: State, mutation: Mutation) -> State {
var newState: State = state
switch mutation {
case .increaseValue:
newState.value += 1; break;
case .decreaseValue:
newState.value -= 1; break;
case .setLoading(let isLoading):
newState.isLoading = isLoading
}
return newState
}
}
// MARK: - Action -> Mutation
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .increase:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.increaseValue)
.delay(.seconds(1), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
case .decrease:
return Observable.concat([
Observable.just(Mutation.setLoading(true)),
Observable.just(Mutation.decreaseValue)
.delay(.seconds(1), scheduler: MainScheduler.instance),
Observable.just(Mutation.setLoading(false))
])
}
}
mutate(action: Action) 함수에서, Action에 대한 분기 처리가 이루어진다.
// MARK: - Mutation -> State
func reduce(state: State, mutation: Mutation) -> State {
var newState: State = state
switch mutation {
case .increaseValue:
newState.value += 1; break;
case .decreaseValue:
newState.value -= 1; break;
case .setLoading(let isLoading):
newState.isLoading = isLoading
}
return newState
}
reduce(state: State, mutation: Mutation) 함수에서, 이전 상태와 Mutation을 받아서 다음 상태를 반환한다.
전체 소스 코드: https://github.com/Benedicto-H/Counter