ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • ReactorKit
    Library 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

    'Library' 카테고리의 다른 글

    UIKitPlus - RootViewController  (0) 2021.06.23
    UIKitPlus - Constraints  (0) 2021.06.19
    UIKitPlus  (0) 2021.06.14
    Swift Package Manager 설치 및 사용법  (0) 2021.03.03
    CocoaPods 설치 및 사용  (0) 2021.03.03
Designed by Tistory.