ABOUT ME

Today
Yesterday
Total
  • ReactorKit
    Architecture + Design Pattern 2022. 10. 12. 01:03

    ReactorKit 이란 ? 

    단방향 데이터 흐름을 가진 반응형 앱을 위한 Framework

    View: 

    사용자 입력을 받아서 Reactor 에 전달 (Action)

    Reactor로부터 받은 상태를 Rendering

    ViewController, Cell, Control 등을 모두 View 로 취급

     

    protocol View {
        associatedtype Reactor
        
        var disposeBag: DisposeBag
        
        // self.reactor가 바뀌면 호출됨.
        func bind(reactor: Reactor)
    }

     

     

    Reactor

    View 에서 전달받은 Action 에 따라 로직 수행

    상태를 관리하고 상태가 변경되면 View 에 전달

    대부분의 View 는 대응되는 Reactor 를 가짐

    protocol Reactor {
        associatedtype Action
        associatedtype Mutation
        associatedtype State
        
        var initialState: State
    }

     

    State 는 Reactor Class 에 저장, 원하는 어떤 dataType 이든 사용할 수 있음. 

     

    Data Flow

    비동기 타임에 State 가 변경되는 경우가 있음.

    Action 과 State 사이에 Mutation 을 두어 비동기 처리

    Mutation 은 State 를 변경하는 명령 / 작업 단위

    Mutation 은 View 에 노출되지 않음

     

     

     

     

     

    예시 : Following Btn 

    class ProfileViewReactor: Reactor {
        enum Action {
            case follow // 사용자 입력
        }
        
        enum Mutation {
            case setFollowing(Bool) // 상태 변경
        }
        
        struct State {
            var isFollowing: Bool // 뷰 상태
        }
    }

     

     

     

     

     

    Sample Code 

     

    1. ViewController

    
    import UIKit
    
    import ReactorKit
    import RxCocoa
    import RxSwift
    
    // Conform to the protocol `View` then the property `self.reactor` will be available.
    final class CounterViewController: UIViewController, StoryboardView {
        
        @IBOutlet var decreaseButton: UIButton!
        @IBOutlet var increaseButton: UIButton!
        @IBOutlet var valueLabel: UILabel!
        @IBOutlet var activityIndicatorView: UIActivityIndicatorView!
        
        var disposeBag = DisposeBag()
        
        // Called when the new value is assigned to `self.reactor`
        // AppDelegate 에서 reactor 할당됨. 
        func bind(reactor: CounterViewReactor) {
            // Action (View -> Reactor) 
            increaseButton.rx.tap               // Tap event
                .map { Reactor.Action.increase }  // Convert to Action.increase
                .bind(to: reactor.action)         // Bind to reactor.action
                .disposed(by: disposeBag)
            
            decreaseButton.rx.tap
                .map { Reactor.Action.decrease }
                .bind(to: reactor.action)
                .disposed(by: disposeBag)
            
            
    //         reactor 값이 변하면 해당 함수 호출,
    //         reactor 의 state 에 따라 .bind(to:) 을 이용해서 View 의 상태 바꿔줌.
    //         State 가 변할 때 value 이외의 값이 변할 수 있으므로 .distinctUntilChanged() 추가.
    //         .map 으로 value 값만 이용한다고 알려줌.
    //		   State ( Reactor -> View )
    		
            reactor.state
                .map { $0.value }   // 10
                .distinctUntilChanged()
                .map { "\($0)" }               // "10"
                .bind(to: valueLabel.rx.text)  // Bind to valueLabel
                .disposed(by: disposeBag)
            
            reactor.state
                .map { $0.isLoading }
                .distinctUntilChanged()
                .bind(to: activityIndicatorView.rx.isAnimating)
                .disposed(by: disposeBag)
            
            reactor.pulse(\.$alertMessage)
                .compactMap { $0 }
                .subscribe(onNext: { [weak self] message in
                    let alertController = UIAlertController(
                        title: nil,
                        message: message,
                        preferredStyle: .alert
                    )
                    alertController.addAction(UIAlertAction(
                        title: "OK",
                        style: .default,
                        handler: nil
                    ))
                    self?.present(alertController, animated: true)
                })
                .disposed(by: disposeBag)
        }
    }

     

     

    2. Reactor

    
    
    import ReactorKit
    import RxSwift
    
    final class CounterViewReactor: Reactor {
      // Action is an user interaction
      enum Action {
        case increase
        case decrease
      }
    
      // Mutate is a state manipulator which is not exposed to a view
      enum Mutation {
        case increaseValue
        case decreaseValue
        case setLoading(Bool)
        case setAlertMessage(String)
      }
    
      // State is a current view state
      struct State {
        var value: Int
        var isLoading: Bool
        @Pulse var alertMessage: String?
      }
    
      let initialState: State
    
      init() {
        self.initialState = State(value: 0, isLoading: false) // start from 0
      }
    
      // 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(.milliseconds(500), scheduler: MainScheduler.instance),
            Observable.just(Mutation.setLoading(false)),
            Observable.just(Mutation.setAlertMessage("increased!")),
          ])
    
        case .decrease:
          return Observable.concat([
            Observable.just(Mutation.setLoading(true)),
            Observable.just(Mutation.decreaseValue).delay(.milliseconds(500), scheduler: MainScheduler.instance),
            Observable.just(Mutation.setLoading(false)),
            Observable.just(Mutation.setAlertMessage("decreased!")),
          ])
        }
      }
    
      // Mutation -> State
      func reduce(state: State, mutation: Mutation) -> State {
        var state = state
        switch mutation {
        case .increaseValue:
          state.value += 1
    
        case .decreaseValue:
          state.value -= 1
    
        case let .setLoading(isLoading):
          state.isLoading = isLoading
    
        case let .setAlertMessage(message):
          state.alertMessage = message
        }
        return state
      }
    }

     

     

     

    출처  ReactorKit OpenSource :              https://github.com/ReactorKit/ReactorKit

    전수열님 ReactorKit 소개 Presentation : https://www.youtube.com/watch?v=ASwBnMJNUK4

     

    GitHub - ReactorKit/ReactorKit: A library for reactive and unidirectional Swift applications

    A library for reactive and unidirectional Swift applications - GitHub - ReactorKit/ReactorKit: A library for reactive and unidirectional Swift applications

    github.com

     

    'Architecture + Design Pattern' 카테고리의 다른 글

    Clean Architecture + MVVM  (0) 2022.10.03
    MVVM  (0) 2022.09.26
    Clean Architecture  (0) 2022.09.25
    Delegate & Protocol  (0) 2022.03.28
    Architecture Patterns ( MVC, MVP,MVVM ) ( with iOS, Swift)  (0) 2021.10.21
Designed by Tistory.