[Swift project] ReactorKit 예제 : Counter

김영민·2022년 5월 4일
0

ReactorKit이란?

하나의 모델을 의미하며, 크게 ViewReactor가 있습니다.
View에서 사용자가 일으키는 Action을 Reactor에 전달하고,
Reactor에서 State의 변화를 View에 전달하는 방식으로 단방향 반응형 아케틱쳐의 형태를 띄고 있습니다.


저는 많이들 처음 구현 해보는 Counter 앱을 통해 ReactorKit을 사용해보겠습니다.

'-'와 '+'를 통해 값을 올리고 내릴 수 있는 기능 구현

Reactor 부분

import Foundation
import ReactorKit


class CounterReactor: Reactor {
    
    let initialState = State()
    
    //View의 Action 정의
    enum Action {
        case increase
        case decrease
    }
    
    //Action을 받을 Mutation 정의
    enum Mutation {
        case increaseValue
        case decreaseValue
    }
    
    struct State {
        var value : Int = 0
    }
    
    func mutate(action: Action) -> Observable<Mutation> {
        switch action {
        case .increase:
            return Observable.just(Mutation.increaseValue)
        case .decrease:
            return Observable.just(Mutation.decreaseValue)
        }
    }
    
    func reduce(state: State, mutation: Mutation) -> State {
        var newState = state
            switch mutation {
            case .increaseValue:
                newState.value += 1
            case .decreaseValue:
                newState.value -= 1
            }
        return newState
    }
}

제가 개인적으로 이해한 느낌은 아래와 같습니다.

  1. View에서 하는 Action을 정의한다.
  2. 받은 Action을 Mutation이라는 Reactor의 한 부분으로 흘려보낸다.
  3. mutate 메소드가 2번의 흘려주는 역할을 한다.
  4. reduce 메소드가 흘려받은 mutation에 따라 state를 변경한다.

View 부분

import UIKit
import ReactorKit
import SnapKit
import RxSwift
import RxCocoa

class ViewController: UIViewController,View {
    //reactorkit에서 View 레이어는 항상 View Protocol을 채택해야함, Disposebag과 bind(reactor:)를 정의해야 함
    var disposeBag = DisposeBag()
    
    init(reactor: CounterReactor) {
        super.init(nibName: nil, bundle: nil)
        self.reactor = reactor
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    private lazy var increaseButton : UIButton = {
        let button = UIButton()
        
        button.setImage(UIImage(systemName: "plus"), for: .normal)
        
        return button
    }()
    
    private lazy var decreaseButton : UIButton = {
        let button = UIButton()
        
        button.setImage(UIImage(systemName: "minus"), for: .normal)
        return button
    }()
    
    private lazy var valueLabel : UILabel = {
        let label = UILabel()
        
        label.font = .systemFont(ofSize: 18.0, weight: .semibold)
        
        return label
    }()
    private lazy var stackView : UIStackView = {
        let stackView = UIStackView()
        
        stackView.axis = .horizontal
        stackView.alignment = .fill
        stackView.distribution = .equalSpacing
        
        [decreaseButton,valueLabel,increaseButton]
            .forEach{stackView.addArrangedSubview($0)}
        
        
        return stackView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setup()
    }
    
    func setup() {
        view.addSubview(stackView)
        
        stackView.snp.makeConstraints{
            $0.leading.trailing.equalToSuperview().inset(24.0)
            $0.centerY.centerX.equalToSuperview()
        }
    }
    
    
    func bind(reactor: CounterReactor) {
        
        //decrease increase 버튼 tap시 action 발생
        
        decreaseButton.rx.tap
            .map{Reactor.Action.decrease}
            .bind(to:reactor.action)
            .disposed(by: disposeBag)
        
        increaseButton.rx.tap
            .map{Reactor.Action.increase}
            .bind(to:reactor.action)
            .disposed(by: disposeBag)
        
        reactor.state
            .map{$0.value}
            .distinctUntilChanged()
            .map{"\($0)"}
            .bind(to: valueLabel.rx.text)
            .disposed(by: disposeBag)
    }
}

  • 우선 저는 스토리보드 없이 구현하였으므로 ViewController에 View 프로토콜을 추가하였습니다.
  • bind 부분을 보면, 버튼 탭 시 reactor로 바인딩해줍니다.
  • reactor의 state는 View의 valueLabel로 바인딩 해줍니다.
  • distinctUntilChanged() : 연달아 중복된 값이 올 때 무시하는 것.

SceneDelegate 부분

  func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
     
        guard let windowScene = (scene as? UIWindowScene) else { return }
        
        self.window = UIWindow(windowScene: windowScene)
        let reactor = CounterReactor()
        let rootViewController = ViewController(reactor: reactor)
        
        window?.backgroundColor = .systemBackground

        window?.rootViewController = UINavigationController(rootViewController: rootViewController)
        window?.makeKeyAndVisible()

    }

위와 같이 Reactor를 정의해주고 ViewController에 저장해주는 작업을 해주어야 실행됩니다!


느낀점

오늘 이렇게 ReactorKit에 대해 간단하게 알아보았는데, 아직 간단한 수준이라 더 공부할 필요가 있어보입니다.
개인적으로는 MVVM보다 눈에 더 잘 들어오는 느낌이 들고, 깔끔한 느낌이라 마음에 들었습니다 !
다음에 또 ReacotrKit 예제를 들고 오겠습니다 ! 감사합니다!

전체코드 : https://github.com/youngmin5068/Counter_reactorkit

0개의 댓글