RxSwift Тема не запускается при вызове - PullRequest
3 голосов
/ 08 ноября 2019

У меня есть приложение, использующее MVP с шаблоном Coordinator.

Когда отправляет дочерний координатор и событие, я ожидаю, что мой AppCoordinator рекурсивно вызовет метод, который выбирает следующий координатор на основена некоторых SessionState.

Основной поток приложения выглядит следующим образом -

AppCoordinator

  1. start() вызывает coordinateToRoot с начальнымstate
  2. Подписывается на showStartScene(), который запускает дочерний координатор

StartCoordinator

  1. start() создает MVP модуль, который теперь виденпользователю
  2. MVP модуль вызывает AuthSvc, который выполняет асинхронный вызов iDP и подтверждает состояние авторизации
  3. По завершении этой задачи публикует событие, которое регистрируется подпиской в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?

...