-
ReactorKitLibrary 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의 장점을 그대로 가져와 좀 더 쉽게 프로그래밍할 수 있을것 같다고 느꼈다.
다만 아쉬웠던건 써드파티 라이브러리이기에? 나중에는 언젠간..? 애플에서 다른 방식으로 라이브러리를 지원한다면 거길 따라가야하겠지만 그건 그때가서 생각하기로😀
이번 포스팅에선 맛보기와 감을 잡기위함이였고 이후에 적용을 시키며 더 발전된 포스팅을 해보기로🏃🏻♂️
[참고자료]
'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