iOS/ReactorKit

[ReactortKit] ReactortKit 슥 알아보기

최지철 2024. 6. 9. 22:12
728x90
반응형

https://github.com/ReactorKit/ReactorKit

ReactorKit?

ReSwift와 Flux를 융합하여 만든 아키텍처입니다.

ReactorKit는 iOS 애플리케이션 개발을 위해 사용되는 프레임워크로, 단방향 데이터 흐름을 중심으로 한 반응형 프로그래밍을 구현할 수 있도록 도와줍니다

 

ReactorKit 공식 문서에 따르면 본 아키텍처의 디자인 목표에 따르면 아래와 같습니다.

  1. 테스트 용이성
    • ReactorKit은 상태와 로직을 분리하여, 테스트 작성이 용이하도록 설계되었습니다. Reactor는 입력(Action)에 따라 상태(State)를 출력하기 때문에, 입력과 출력을 테스트함으로써 비즈니스 로직의 단위 테스트를 쉽게 작성할 수 있습니다.
  2. 코드의 가독성 및 유지 보수성
    • ReactorKit은 복잡함을 피하는 데 중점을 두었습니다.
    • Reactor는 상태와 로직을 관리하고, View는 상태를 구독하여 UI를 업데이트합니다. 이를 통해 코드의 복잡성을 줄이고, 유지보수와 확장이 쉬워집니다.

사용자의 Action은 Reactor로, Reactor에서 방출된 State는 View로 Observable 스트림을 통해 전달됩니다.

이러한 흐름은 단방향적입니다.

View는 Action만 방출 할 수 있으며, Reactor는 State만 방출 할 수 있습니다.

단방향적 흐름으로, View와 비즈니스 로직을 분리 할 수 있으며 모듈 간 결합도가 낮게 개발할 수 있습니다.

ReactorKit의 핵심 개념

 

  • Reactor: Reactor는 상태(state)와 액션(action)을 정의하는 중심적인 역할을 합니다. 상태는 애플리케이션의 현재 상태를 나타내며, 액션은 상태를 변화시키는 이벤트를 의미합니다. Reactor는 상태와 액션을 기반으로 상태 변화를 처리하고, 새로운 상태를 뷰에 전달합니다.
  • State: State는 뷰가 표시해야 하는 데이터를 나타냅니다. 이는 Reactor에서 관리되며, Reactor는 상태 변화를 감지하고 이를 뷰에 반영합니다.
  • Action: Action은 사용자의 입력이나 다른 외부 이벤트를 나타냅니다. 사용자는 버튼 클릭, 텍스트 입력 등 다양한 방식으로 액션을 발생시킬 수 있으며, Reactor는 이러한 액션을 처리하여 상태를 변경합니다.
  • Mutation: Mutation은 액션에 의해 상태가 실제로 변경되는 과정을 의미합니다. Reactor는 액션을 Mutation으로 변환하여 새로운 상태를 생성합니다.
  • View: View는 상태를 기반으로 사용자에게 UI를 제공합니다. ReactorKit에서는 뷰가 직접 상태를 관리하지 않고, Reactor로부터 상태를 전달받아 UI를 업데이트합니다.

 

간단한 예제로 살펴보기

Reactor 코드

mutate() reduce() 과정을 거쳐서 State Stream으로 바꾸어 다시 View로 전달해주는 역할을 합니다.

Reactor 프로토콜을 적용해야하며, 그 내부에는 사용자 인터랙션을 의미하는 Action, Action과 State의 매개체인 Mutation, View가 가질 상태를 의미하는 State 를 반드시 정의해야 합니다. 또한, State의 초기값을 설정하기 위해 initialState가 필요합니다.

mutate() 함수는 Action 스트림을 Mutation 스트림으로 변환하고, 변환된 Mutation 스트림은 reduce() 함수로 전달됩니다.

reduce() 함수는 이전 State와 Mutation을 활용하여 새로운 State를 반환합니다. 이 State를 View에서 구독을 하고 있었다면, State가 변경됨에 따라서, UI가 업데이트 될 것입니다.

import ReactorKit
import RxSwift

// Reactor 정의
class CounterReactor: Reactor {
    // 상태 정의
    struct State {
        var count: Int
    }
    
    // 액션 정의
    enum Action {
        case increment
        case decrement
    }
    
    // 초기 상태
    let initialState = State(count: 0)
    
    // 상태 변화를 처리하는 함수
    func mutate(action: Action) -> Observable<Action> {
        switch action {
        case .increment:
            return Observable.just(.increment)
        case .decrement:
            return Observable.just(.decrement)
        }
    }
    
    // 새로운 상태를 생성하는 함수
    func reduce(state: State, mutation: Action) -> State {
        var newState = state
        switch mutation {
        case .increment:
            newState.count += 1
        case .decrement:
            newState.count -= 1
        }
        return newState
    }
}


ViewController 코드

View는 Reactor로 받는State를 통해 UI를 보여주는 역할과, 사용자 인터랙션을 추상화하여 Reactor로 전달하는 역할을 수행합니다.

View는 Reactor를 향해 Action을 방출하고 있으며, Reactor는 View를 향해 State를 방출해주고 있습니다.

이를 적용하기 위해선, View 프로토콜을 적용해야하며, 그 안에 DisposeBag 프로퍼티와 bind(reactor:) 메서드를 반드시 정의해주어야 합니다.

bind 내부에는, Reactor로 보낼 Action과, Reactor로부터 수신할 State를 작성해야합니다.

// ViewController에서 Reactor 사용
class CounterViewController: UIViewController, View {
    var disposeBag = DisposeBag()
    let counterLabel = UILabel()
    let incrementButton = UIButton()
    let decrementButton = UIButton()
    
    // Reactor를 바인딩하는 함수
    func bind(reactor: CounterReactor) {
        // 액션 바인딩
        incrementButton.rx.tap
            .map { Reactor.Action.increment }
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        
        decrementButton.rx.tap
            .map { Reactor.Action.decrement }
            .bind(to: reactor.action)
            .disposed(by: disposeBag)
        
        // 상태 바인딩
        reactor.state
            .map { "\($0.count)" }
            .bind(to: counterLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

 

728x90
반응형