EEYatHo 앱 깎는 이야기

Swift ) ReactorKit (1) 개념, 사용법 - EEYatHo iOS 본문

iOS, Swift/ArchitecturePattern

Swift ) ReactorKit (1) 개념, 사용법 - EEYatHo iOS

EEYatHo 2022. 12. 1. 17:40
반응형

 

ReactorKit


  • 공식 Git 링크
  • View 와 Reactor 을 가지고 화면을 만든다.
  • View = UIView, UIViewController, UITableViewCell 등
    Reactor = ViewModel. 비지니스 로직.
  • ReactorKit 은 FluxReactive Programming 의 조합
  • 부분적으로 도입이 가능하고, 라이브러리인 만큼, 공식 설명이 잘 되어있다.

 

 

 

ReactorKit 의 디자인 목표


  • Testability : 뷰(View)와 비지니스 로직(Reactor)을 분리.
    Reactor는 View 에 대한 종속성이 없음.

  • Small Start : RIBs 처럼 앱 전체가 하나의 아키텍쳐를 따를 필요 없음.
    특정 화면에만 ReactorKit을 사용할 수 있음

  • Less Typing : ReactorKit은 단순한 것을 위한 복잡한 코드를 피하려고함.
    다른 아키텍쳐들에 비해 적은 코드만 필요.
    Reactor 에서,  Action -> mutate -> reduce 과정을 알아서 바인딩 해주기 때문에 매우 편리함.

 

 

 

ReactorKit 사용법


  • View 는 Reactor 에게 Action 이라는 enum 값을 넘기고,
    Reactor 은 View 에게 State 라는 struct 값을 넘긴다.

  • View 는 State 에 들어있는 값으로 화면을 그린다. ( State == ViewModel )

  • View 를 만들기 위해서는, 프로토콜을 채택하고 구현 하면 된다.
class HomeViewController: UIViewController, View {...}

 

  • View 생성시, Reactor 주입 방법
let homeVC = HomeViewController()
homeVC.reactor = HomeReactor() //inject reactor

 

  • Reactor 이 주입되는(reactor 값이 변경되는) 타이밍에 View 의 bind가 호출됨
func bind(reactor: ProfileViewReactor) {
  // action (View -> Reactor)
  refreshButton.rx.tap.map { Reactor.Action.refresh }
    .bind(to: reactor.action)
    .disposed(by: self.disposeBag)

  // state (Reactor -> View)
  reactor.state.map { $0.isFollowing }
    .bind(to: followButton.rx.isSelected)
    .disposed(by: self.disposeBag)
}

 

  • View 는 typealias 로 Reactor 을 지정
typealias Reactor = HomeReactor

 

  • Reactor 도 View 처럼 프로토콜을 채택 및 구현하면 된다.
class HomeReactor: Reactor {...}

 

  • Reactor 에 필요한 Action, Mutation, State 생성
class ProfileViewReactor: Reactor {
  // represent user actions
  enum Action {
    case refreshFollowingStatus(Int)
    case follow(Int)
  }

  // represent state changes
  enum Mutation {
    case setFollowing(Bool)
  }

  // represents the current view state
  struct State {
    var isFollowing: Bool = false
  }

  let initialState: State = State()
}

 

 

 

  • View 는 Reactor 에게 Action 을 날린다.
    Reactor 은 mutate 함수로, Action 을 Mutation 으로 방출하며 Side Effect 작업을 진행한다.
    방출된 Mutation 은 reduce 함수에서 수신한다.

func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // receive an action
    return UserAPI.isFollowing(userID) // create an API stream
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // convert to Mutation stream
      }

  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

 

 

  • reduce 함수는 받은 Mutation 과, 이전 State 를 가지고 새로운 State 를 만들고 방출한다.
    reduce 는 Side Effect 가 발생하는 작업을 하지 않는다. 즉 순수함수이다.
    방출된 State 는 View 에게 전달된다.
func reduce(state: State, mutation: Mutation) -> State {
  var state = state // create a copy of the old state
  switch mutation {
  case let .setFollowing(isFollowing):
    state.isFollowing = isFollowing // manipulate the state, creating a new state
    return state // return the new state
  }
}

 

  • Reactor 는 각 스트림에게 추가적으로 스트림을 구독시킬 수 있는 transform 함수를 선언할 수 있다.
    예를 들어, 유저의 상태값(전역 상태값)이 변하는 것에 대해 반응해야하는 Reactor 가 있다면,
    mutate 함수가 구독하도록 아래와 같이 선언할 수 있다.
var currentUser: BehaviorSubject<User> // global state

func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
  return Observable.merge(mutation, currentUser.map(Mutation.setUser))
}
func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

 

Comments