minaje

ReactorKit 본문

iOS/스택

ReactorKit

minaje 2023. 8. 17. 15:41

Reactor?

출처 - https://medium.com/styleshare/reactorkit-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0-c7b52fbb131a

ReactorKit은 UI를 구성하는 View와 비지니스 로직은 담당하는 Reactor로 이루어져 있다.

View는 알겠는데 Reactor가 뭐지..!!

Reactor는 View에서 일어날 Action을 미리 정의해놓고! 그 Action이 일어나면 State를 View에게 보내는 형식으로 작동한다.

View는 Reactor에게 Action으로 보내고 State를 받아와서! UI를 변경하는 방식으로 작동한다.

자 이렇게 대충 개념은 알았으니 코드를 보며 사용하는 방법을 알아보자!

 

View!

ex) increaseButton 예시

- ReactorKit 내부적으로 호출되는 bind(reactor:) 메소드에서 바인딩 수행

  - bindAction(_:): View에서 Reactor로 이벤트 방출

  - bindState(_:): Reactor에서 바뀐 state들을 View에서 구독

  - storyboard 이용 시 StoryboardView 구현

import UIKit
import RxSwift
import RxCocoa
import ReactorKit

class CounterViewController: UIViewController, StoryboardView {
    
    var disposeBag = DisposeBag()
    
    @IBOutlet weak var countLabel: UILabel!
    @IBOutlet weak var increaseButton: UIButton!
    
    func bind(reactor: CounterViewReactor) {
        bindAction(reactor)
        bindState(reactor)
    }
    
    private func bindAction(_ reactor: CounterViewReactor) {
        increaseButton.rx.tap
            .map { Reactor.Action.increase }
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
    }

    private func bindState(_ reactor: CounterViewReactor) {
        reactor.state
            .map { String($0.value) }
            .distinctUntilChanged()
            .bind(to: countLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

Reactor!

  • Action
    • View로부터 받을 Action을 enum으로 정의
    • ex) increaseButtonDidTap
  • Mutation
    • View로부터 action을 받은 경우, 해야할 작업단위들을 enum으로 정의
    • ex) increaseValue
  • State
    • 현재 상태를 기록하고 있으며, View에서 해당 정보를 사용하여 UI업데이트 및 Reactor에서 image를 얻어올때 page정보들을 저장
  • mutate(action:) -> Observable<Mutation>
    • Action이 들어온 경우, 어떤 처리를 할것인지 Mutation에서 정의한 작업 단위들을 사용하여 Observable로 방출
    • 해당 부분에서, RxSwift의 concat 연산자를 이용하여 비동기 처리가 유용
    • concat 연산자: 여러 Observable이 배열로 주어지면 순서대로 실행
  • reduce(state:mutation:) -> State
    • 현재 상태(state)와 작업 단위(mutation)을 받아서, 최종 상태를 반환
    • mutate(action:) -> Observable<Mutation>이 실행된 후 바로 해당 메소드 실행
import Foundation
import RxSwift
import RxCocoa
import ReactorKit

class CounterViewReactor: Reactor {
    let initialState = State()
    
    enum Action {
        case increase
        case decrease
    }
    
    // 처리 단위
    enum Mutation {
        case increaseValue
        case decreaseValue
        case setLoading(Bool)
    }
    
    // 현재 상태를 기록
    struct State {
        var value = 0
        var isLoading = false
    }
    
    // Action이 들어온 경우, 어떤 처리를 할건지 분기
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .increase:
            return Observable.concat([
                Observable.just(.setLoading(true)),
                Observable.just(.increaseValue).delay(.seconds(1), scheduler: MainScheduler.instance),
                Observable.just(.setLoading(false))
            ])
        case .decrease:
            return Observable.concat([
                Observable.just(.setLoading(true)),
                Observable.just(.decreaseValue).delay(.seconds(1), scheduler: MainScheduler.instance),
                Observable.just(.setLoading(false))
            ])
        }
    }
    
    // 이전 상태와 처리 단위를 받아서 다음 상태를 반환하는 함수
    func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
        switch mutation {
        case .increaseValue:
            newState.value += 1
        case .decreaseValue:
            newState.value -= 1
        case .setLoading(let isLoading):
            newState.isLoading = isLoading
        }
        return newState
    }
}

참고 - https://ios-development.tistory.com/782