발견한 Coordinator의 문제점
- Rx를 대부분으로 사용하는 프로젝트에서 Coordinator만 Reactive하지 않다는 문제점
- 사용자 액션 및 이벤트를 Coordinator에게 전달할 때, 넘어가야할 객체의 수가 많은 경우가 존재
- 기존에는 Delegate Pattern으로 이벤트를 전달했지만 중복된 코드가 많아지고 코드의 가시성이 현저히 떨어짐
- 위 사진은 일반적인 경우지만 더 많은 하나의 이벤트가 훨씬 많은 Delegate 메서드를 필요로 하는 경우도 존재
RxFlow
- 코디네이터 패턴에 ReactiveX(rx)를 접목시켜 만든 라이브러리
- 고전적인 Delegate Pattern을 탈피하고 Flows를 이용하여 네비게이션이 좀 더 명확해짐
- FlowCoordinator를 기본적으로 제공하여 Flows사이의 네비게이션을 제어 가능
- 네비게이션 액션들이 Reactive하기 이루어짐 ⭐️
- RxFlow에 관련한 개념 및 소스코드는 여기서 참조 가능
화면 간 Flow 구현
- 위 사진은 RxFlow를 사용하기 전 각 화면을 도식화하는 작업
- 공통된 성격을 가지고 있는 ViewController끼리 하나의 Flow로 분리
- 앱이 실행 될 때에는 AppFlow로 부터 시작하여 다른 Flow들을 관리함
- 여기서 Flow는 Coordinator라고 생각해도 무방
- 예를 들어 AuthFlow는 로그인, 회원가입에 대한 화면들만 담당하여 로직을 처리하는 객체
@MainActor
public final class AppFlow: Flow {
// MARK: - Properties
public var root: Presentable { self.rootViewController }
/// Used only for testFlow.
private let rootViewController: FavorTabBarController
// Comment this Initializer.
public init() {
self.rootViewController = FavorTabBarController()
}
// MARK: - Navigate
public func navigate(to step: Step) -> FlowContributors {
guard let step = step as? AppStep else { return .none }
switch step {
case .splashIsRequired:
return self.navigateToSplash()
case .splashIsComplete:
return self.popFromSplash()
case .dashboardIsRequired:
return self.popToDashboard()
case .authIsRequired:
return self.navigateToAuth()
case .localAuthIsRequired(let location):
return self.navigateToLocalAuth(request: location)
case .localAuthIsComplete:
return self.popToDashboard()
case .wayBackToRootIsRequired:
return self.navigateWayBackToRoot()
case .giftManagementIsRequired:
return self.navigateToGiftManagement()
default:
return .none
}
}
}
- 최상위에 있는 AppFlow의 구현 모습
- Flow 객체는 Flow 프로토콜을 채택하고 navigate(to:)메서드를 통해 화면이 전환된다.
func navigateToAuth() -> FlowContributors {
let authFlow = AuthFlow()
Flows.use(authFlow, when: .created) { root in
self.rootViewController.dismiss(animated: false) {
root.modalPresentationStyle = .overFullScreen
self.rootViewController.present(root, animated: false)
}
}
return .one(flowContributor: .contribute(
withNextPresentable: authFlow,
withNextStepper: OneStepper(
withSingleStep: AppStep.authIsRequired
)
))
}
- 만약 .authIsRequired의 액션이 들어오면 navigateToAuth()가 호출
- RxFlow만의 네이밍으로 바뀌었지만 전체적인 개념은 Coordinator와 동일
- FlowContributors는 AuthFlow를 하위 Flow로 추가하고 AuthFlow에 있는 .authIsRequired를 호출하는 역할
- Flows.use의 @escaping Closure를 통해 AuthFlow의 rootView를 가지고 화면 전환이 가능
public final class AuthSignInViewReactor: Reactor, Stepper {
public var steps = PublishRelay<Step>()
public func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .signInButtonDidTap:
let email = self.currentState.email
let password = self.currentState.password
return .concat([
self.requestSignIn(email: email, password: password)
.flatMap { _ in
ReminderAlertManager.shared.fetchReminders()
self.steps.accept(AppStep.authIsComplete) 🟢
return Observable<Mutation>.empty()
}
])
case .findPasswordButtonDidTap:
self.steps.accept(AppStep.findPasswordIsRequired) 🟢
return .empty()
}
}
- ViewModel에 Stepper라는 프로토콜을 채택하면 steps라는 프로퍼티를 구현
- 이 steps는 현재 보여지고 있는 Flow를 통해 원하는 Step을 넘겨줌
- 해당 Flow는 원하는 Action을 받아서 로직을 수행할 수 있는 구조