Library

ReactorKit

GREEN.1229 2021. 6. 16. 10:55

안녕하세요. 그린입니다🟢

이번 포스팅에서는 ReactorKit에 대해 학습해보겠습니다🧑🏻‍💻

ReactorKit?

 - 반응형 단방향 Swift 애플리케이션을 위한 써드파티 라이브러리

 - Flux와 Reactive Programming의 조합으로 이루어짐

 - User Action과 View는 스트림을 통해 각 계층에 전달 (스트림은 단방향)

 - View는 Action만 내보내고 Reactor는 State만 내보낼 수 있음

ReactorKit 사용을 위한 요구사항?

 - Swift 5 이상

 - iOS 8 이상

 - Cocoapods로만 지원 가능 (https://github.com/ReactorKit/ReactorKit)

 ReactorKit의 특징?

 - 기존 MVVM 아키텍쳐 패턴에는 특정 정형화된 템플릿이 없어 개발자들마다 이해하는 사항이 다른 불편함이 존재하였는데 ReactorKit은 규칙을 두어 MVVM의 형태를 정형화하게 템플릿화해서 사용할 수 있음

 - Rx와 MVVM을 사용하면서 다양한 Observable 변수가 생길때 변수의 상태와 액션의 의도 및 구분이 어려운점을 ReactorKit이 해결

 - 비지니스 로직을 분리함으로 테스트에 용이 (뷰에는 비지니스 로직이 없음)

 - 부분적인 아키텍쳐를 따라도됨 (전체가 다 해당 아키텍쳐 패턴을 따르지 않아도됨)

 - 간결한 코드를 통해 단순하게 구현하고 확장이 가능

ReactorKit의 요소?

 1. View

  - 뷰에는 비지니스 로직이 없음

  - 사용자 입력을 Action 스트림에 바인딩

  - 뷰의 State를 각 UI컴포넌트에 바인딩

  - View 프로토콜을 따르고 리액터를 주입하여 사용

class ProfileViewController: UIViewController, View {
    var disposeBag = DisposeBag()
}
profileViewController.reactor = UserViewReactor() // 리액터 주입

  - 바인딩 함수 구현을 통해 Action과 State 스트림 바인딩 정의

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)
}

  - 스토리보드 지원

let viewController =  MyViewController ()
viewController. reactor  =  MyViewReactor () // `bind (reactor :)`를 즉시 실행하지 않습니다.

class  MyViewController : UIViewController , StoryboardView {
   func  bind ( reactor : MyViewReactor) {
     // 뷰가로드 된 후 호출됩니다 (viewDidLoad).
  }
}

 

 2. Reactor (ViewModel)

  - 뷰 상태 관리 (UI와는 독립)

  - 뷰와 각 1:1 대응되는 Reactor를 가짐

  - 뷰와 의존성이 없어 테스트에 용이

  - Action, Mutation, State 타입을 정의해줘야함

  - 모든 로직이 Reactor에 위임됨

class  ProfileViewReactor : Reactor {
   // 사용자 작업을 나타냅니다.
  enum  Action {
     case  refreshFollowingStatus ( Int )
     case  follow ( Int )
  }
  // 상태 변경을 나타냅니다.
  enum  Mutation {
     case  setFollowing ( Bool )
  }
  // 현재 뷰 상태를 나타냅니다.
  struct  State {
     var isFollowing : Bool  =  false
  }
  let initialState : State =  State ()
}

 

  - Action: 사용자 입력 및 상호작용

  - State: 뷰 상태

  - Mutation: Action과 State 사이 중간자 역할로 mutate(), reduce() 메서드를 이용해 Action스트림과 State스트림으로 변환 역할

Reactor의 Action -> State 스트림 변경 메서드 단계?

 1. mutate()

  - 뷰로 Action을 받고 Observable<Mutation>을 생성

func mutate(action: Action) -> Observable<Mutation>

  - 비동기 작업 및 API 호출과 같은 사이드 이펙트의 모든 처리는 mutate() 메서드 안에서 수행

func mutate(action: Action) -> Observable<Mutation> {
  switch action {
  case let .refreshFollowingStatus(userID): // 액션 받음
    return UserAPI.isFollowing(userID) // API stream 만듬
      .map { (isFollowing: Bool) -> Mutation in
        return Mutation.setFollowing(isFollowing) // Mutation stream으로 변환
      }
  case let .follow(userID):
    return UserAPI.follow()
      .map { _ -> Mutation in
        return Mutation.setFollowing(true)
      }
  }
}

 

2. reduce()

  - 기존 State와 mutation으로부터 새로운 State 생성

func reduce(state: State, mutation: Mutation) -> State

  - 순수함수로 동기적으로 새로운 State를 리턴해야함 (사이드 이펙트를 수행하면 안됨)

func reduce(state: State, mutation: Mutation) -> State {
  var state = state // 기존 State 복사
  switch mutation {
  case let .setFollowing(isFollowing):
    state.isFollowing = isFollowing // State 이용해 새로운 State 생성
    return state // 새로운 State 반환
  }
}

 

3. transform()

  - 각 스트림을 상황에 맞는 메서드를 호출해 변형

func transform(action: Observable<Action>) -> Observable<Action>
func transform(mutation: Observable<Mutation>) -> Observable<Mutation>
func transform(state: Observable<State>) -> Observable<State>

 

 

[소감]

Composable 패턴과 UIKitPlus를 익히면서 연관지어 ReactorKit을 맛보면서 이해해봤는데 너무 재밌다...!!🙌

SwiftUI와 Combine을 사용하는것도 물론 좋지만 왜 ReactorKit을 사용하느냐를 조금 생각해보았다.

우선 타겟층이나 사용할 수 있는 환경이 ReactorKit은 iOS 8 이상이라 어떻게보면 유저 타겟층을 신경쓰지 않아도된다.

또한, ReactorKit을 쓰는게 아마 아직 Rx가 낯설은 개발자들도 많고 다 제각기의 형태를 가질텐데 나름 정형화된 템플릿이 구축되어 있어 유의할점만 신경쓰며 따르기만하면 된다는것도 놀랍다.

무엇보다 스트림이 단방향이라 버그 파악에도 용이하고 테스트도 편해지고 의존성도 분리되고..! MVVM을 지향하면서 좀 더 간결하게 할 수 있는점이 편하게 느껴졌다.

UIKitPlus와 같이 사용된다면 타겟층을 많이 고려하지 않아도 Combine과 SwiftUI의 장점을 그대로 가져와 좀 더 쉽게 프로그래밍할 수 있을것 같다고 느꼈다.

다만 아쉬웠던건 써드파티 라이브러리이기에? 나중에는 언젠간..? 애플에서 다른 방식으로 라이브러리를 지원한다면 거길 따라가야하겠지만 그건 그때가서 생각하기로😀

이번 포스팅에선 맛보기와 감을 잡기위함이였고 이후에 적용을 시키며 더 발전된 포스팅을 해보기로🏃🏻‍♂️

 

[참고자료]

https://github.com/ReactorKit/ReactorKit

 

ReactorKit/ReactorKit

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

github.com