-
ReactorKitArchitecture + 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