ReactorKit 공부하기 #1
해당 포스팅은 ReactorKit 3.2.0을 기준으로 함
오늘은 ReactorKit에 대해서 공부해보려고 함.
MVVM을 사용하고 있는데 무신사 등에서 리액터 킷을 사용하고 있대서 학습해보고자 함.
오픈소스 링크 및 설명
Basic Concept
ReactorKit은 반응적이고 단방햑적인 Swift 아키텍처를 위한 프레임워크
ReactorKit은 Flux와 Reactive Programming의 조합
- user의 Action과 View의 상태는 observable streams을 통해 각각의 레이어에 전달
- 이러한 스트림은 단방향 스트림이고, 뷰는 오로지 emit action만 방출할 수 있고 reactor는 state만 방출할 수 있.
코드 예시
Reactor를 정의하기
//
// CounterViewReactor.swift
// ReactorKitPractice
//
// Created by Hamlit Jason on 2022/07/23.
//
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이 들어온 경우 어떤 처리를 할 것인지 분기
///
/// Mutation에서 정의한 작업 단위들을 사용하여 Observable로 방출
///
/// 액션에 맞게 행동해!
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .increase:
return Observable.concat([ // 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))
])
}
}
/// 이전 상태와 처리 단위를 받아서 다음 상태를 반환하는 함수
///
/// mutate(action: )이 실행되고 난 후 바로 해당 메소드를 실행
///
/// 변화에 맞게끔 값을 설정해!
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
}
}
CounterViewController를 정의하고 리액터 연결하기
//
// CounterViewController.swift
// ReactorKitPractice
//
// Created by Hamlit Jason on 2022/07/23.
//
import UIKit
import RxSwift
import RxCocoa
import ReactorKit
import SnapKit
class CounterViewController: UIViewController, View {
var disposeBag = DisposeBag()
let counterViewReactor = CounterViewReactor()
lazy var increaseButton = UIButton()
lazy var countLabel = UILabel()
lazy var decreaseButton = UIButton()
lazy var loadingIndicator = UIActivityIndicatorView()
override func viewDidLoad() {
super.viewDidLoad()
setupView()
bind(reactor: counterViewReactor)
}
func bind(reactor: CounterViewReactor) {
bindAction(reactor: reactor)
bindState(reactor: reactor)
}
private func bindAction(reactor: CounterViewReactor) {
increaseButton.rx.tap
.map { CounterViewReactor.Action.increase }
.bind(to: reactor.action)
.disposed(by: disposeBag)
decreaseButton.rx.tap
.map { CounterViewReactor.Action.decrease }
.bind(to: reactor.action)
.disposed(by: disposeBag)
}
private func bindState(reactor: CounterViewReactor) {
reactor.state
.map { "\($0.value)" }
.distinctUntilChanged()
.bind(to: countLabel.rx.text)
.disposed(by: disposeBag)
reactor.state
.map { $0.isLoading }
.distinctUntilChanged()
.bind(to: loadingIndicator.rx.isAnimating)
.disposed(by: disposeBag)
reactor.state
.map { !$0.isLoading }
.distinctUntilChanged()
.bind(to: loadingIndicator.rx.isHidden)
.disposed(by: disposeBag)
}
}
extension CounterViewController {
var margin: CGFloat {
get { return 10.0 }
}
func setupView() {
view.addSubview(increaseButton)
increaseButton.setImage(UIImage(systemName: "plus"), for: .normal)
increaseButton.snp.makeConstraints {
$0.width.height.equalTo(30)
$0.centerY.equalToSuperview()
$0.leading.equalTo(margin)
}
view.addSubview(countLabel)
countLabel.text = "0"
countLabel.snp.makeConstraints {
$0.center.equalToSuperview()
}
view.addSubview(decreaseButton)
decreaseButton.setImage(UIImage(systemName: "minus"), for: .normal)
decreaseButton.snp.makeConstraints {
$0.width.height.equalTo(30)
$0.centerY.equalToSuperview()
$0.trailing.equalTo(-margin)
}
view.addSubview(loadingIndicator)
loadingIndicator.snp.makeConstraints {
$0.centerX.equalToSuperview()
$0.centerY.equalToSuperview().offset(100)
}
}
}
'apple > RxSwift, ReactorKit' 카테고리의 다른 글
[ReactorKit] ReactorKit 공부하기 #3 RxTodo 따라잡기 (1) (0) | 2022.09.07 |
---|---|
[ReactorKit] ReactorKit 공부하기 #2 (0) | 2022.07.24 |
RxSwift Signal,Emit (0) | 2022.02.19 |
iOS RxAction (RxSwift Community) (0) | 2022.01.19 |
RxSwift ch 18. Table & Collection views (0) | 2022.01.18 |