У меня есть приложение, использующее MVP
с шаблоном Coordinator
.
Когда отправляет дочерний координатор и событие, я ожидаю, что мой AppCoordinator
рекурсивно вызовет метод, который выбирает следующий координатор на основена некоторых SessionState
.
Основной поток приложения выглядит следующим образом -
AppCoordinator
start()
вызывает coordinateToRoot
с начальнымstate - Подписывается на
showStartScene()
, который запускает дочерний координатор
StartCoordinator
start()
создает MVP
модуль, который теперь виденпользователю MVP
модуль вызывает AuthSvc
, который выполняет асинхронный вызов iDP и подтверждает состояние авторизации - По завершении этой задачи публикует событие, которое регистрируется подпиской в
AppCoordinator
метод coordinateToRoot
и цикл повторяется с использованием соответствующего координатора для состояния просмотра.
Однако проблема заключается в том, что при публикации этого события ничего не происходит. start()
не показывает, что получило событие, и coordinateToRoot
больше не вызывается.
Я создал самую базовую версию, которую я могу ниже, чтобы продемонстрировать это. Я также жестко запрограммировал showStartScene
, чтобы вернуть .signedIn
вместо просмотра состояния аутентификации.
В приведенном ниже примере я ожидал бы, что после загрузки представления presenter.signal
должно немедленно вызвать событиеэто приводит к отображению оператора печати.
SessionState
enum SessionState: String {
case unknown, signedIn, signedOut
}
AppCoordinator
final class AppCoordinator: BaseCoordinator<Void> {
private let window: UIWindow
init(window: UIWindow) {
self.window = window
}
override func start() -> Observable<Void> {
coordinateToRoot(basedOn: .unknown)
return .never()
}
/// Recursive method that will restart a child coordinator after completion.
/// Based on:
/// https://github.com/uptechteam/Coordinator-MVVM-Rx-Example/issues/3
private func coordinateToRoot(basedOn state: SessionState) {
switch state {
case .unknown:
return showStartScene()
.subscribe(onNext: { [unowned self] state in
self.window.rootViewController = nil
self.coordinateToRoot(basedOn: state)
})
.disposed(by: disposeBag)
case .signedIn:
print("I am signed in")
case .signedOut:
print("I am signed out")
}
}
private func showStartScene() -> Observable<SessionState> {
let coordinator = StartCoordinator(window: window)
return coordinate(to: coordinator).map { return .signedIn }
}
}
StartCoordinator
final class StartCoordinator: BaseCoordinator<Void> {
private(set) var window: UIWindow
init(window: UIWindow) {
self.window = window
}
override func start() -> Observable<CoordinationResult> {
let viewController = StartViewController()
let presenter = StartPresenter(view: viewController)
viewController.configurePresenter(as: presenter)
window.rootViewController = viewController
window.makeKeyAndVisible()
return presenter.signal
}
}
Запустить модуль MVP
protocol StartViewInterface: class {
func configurePresenter(as presenter: StartPresentation)
}
protocol StartPresentation: class {
var viewIsReady: PublishSubject<Void> { get }
var signal: PublishSubject<Void> { get }
}
// MARK:- StartPresenter
final class StartPresenter {
// Input
let viewIsReady = PublishSubject<Void>()
// Output
let signal = PublishSubject<Void>()
weak private var view: StartViewInterface?
private lazy var disposeBag = DisposeBag()
init(view: StartViewInterface?) {
self.view = view
viewIsReady.bind(to: signal).disposed(by: disposeBag)
}
}
extension StartPresenter: StartPresentation { }
// MARK:- StartViewController
final class StartViewController: UIViewController {
private var presenter: StartPresentation?
override func viewDidLoad() {
super.viewDidLoad()
if let presenter = presenter {
presenter.viewIsReady.onNext(())
}
}
}
extension StartViewController: StartViewInterface {
func configurePresenter(as presenter: StartPresentation) {
self.presenter = presenter
}
}
Интересно, что если я сделаю что-то подобное в StartCoordinator
, процесс будет работать, однако он нечто я пытаюсь достичь.
override func start() -> Observable<CoordinationResult> {
let viewController = StartViewController()
let presenter = StartPresenter(view: viewController)
viewController.configurePresenter(as: presenter)
window.rootViewController = viewController
window.makeKeyAndVisible()
let subject = PublishSubject<Void>()
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
subject.onNext(())
}
return subject
}
Для справки мой BaseCoordinator
выглядит следующим образом -
/// Base abstract coordinator generic over the return type of the `start` method.
class BaseCoordinator<ResultType>: CoordinatorType {
/// Typealias which allows to access a ResultType of the Coordainator by `CoordinatorName.CoordinationResult`.
typealias CoordinationResult = ResultType
/// Utility `DisposeBag` used by the subclasses.
let disposeBag = DisposeBag()
/// Unique identifier.
internal let identifier = UUID()
/// 1. Stores coordinator in a dictionary of child coordinators.
/// 2. Calls method `start()` on that coordinator.
/// 3. On the `onNext:` of returning observable of method `start()` removes coordinator from the dictionary.
///
/// - Parameter coordinator: Coordinator to start.
/// - Returns: Result of `start()` method.
func coordinate<T: CoordinatorType, U>(to coordinator: T) -> Observable<U> where U == T.CoordinationResult {
store(coordinator: coordinator)
return coordinator.start()
.do(onNext: { [weak self] _ in self?.free(coordinator: coordinator) })
}
/// Starts job of the coordinator.
///
/// - Returns: Result of coordinator job.
func start() -> Observable<ResultType> {
fatalError(message: "Start method should be implemented.")
}
/// Dictionary of the child coordinators. Every child coordinator should be added
/// to that dictionary in order to keep it in memory.
/// Key is an `identifier` of the child coordinator and value is the coordinator itself.
/// Value type is `Any` because Swift doesn't allow to store generic types in the array.
private(set) var childCoordinators: [UUID: Any] = [:]
/// Stores coordinator to the `childCoordinators` dictionary.
///
/// - Parameter coordinator: Child coordinator to store.
private func store<T: CoordinatorType>(coordinator: T) {
childCoordinators[coordinator.identifier] = coordinator
}
/// Release coordinator from the `childCoordinators` dictionary.
///
/// - Parameter coordinator: Coordinator to release.
private func free<T: CoordinatorType>(coordinator: T) {
childCoordinators[coordinator.identifier] = nil
}
}
EDIT Я добавил несколько операторов debug
и яможно увидеть, что заказ на следующее событие отключен, а подписка
2019-11-08 10:26:19.289: StartPresenter -> subscribed
2019-11-08 10:26:19.340: StartPresenter -> Event next(())
2019-11-08 10:26:19.350: coordinateToRoot -> subscribed
Почему coordinateToRoot
подписывается после создания StartPresenter
?