首页 > 解决方案 > 调用时 RxSwift 主题未触发事件

问题描述

我有一个使用MVPCoordinator模式的应用程序。

当子协调器发送和事件时,我希望我AppCoordinator递归调用一个方法,该方法基于 some 选择下一个协调器SessionState

该应用程序的基本流程如下 -

AppCoordinator

  1. start()coordinateToRoot以初始状态调用
  2. 订阅showStartScene()哪个启动子协调器

StartCoordinator

  1. start()创建MVP现在对用户可见的模块
  2. MVP模块调用AuthSvc对 iDP 进行异步调用并确认身份验证状态
  3. 完成此任务后,发布一个由订阅在AppCoordinator'scoordinateToRoot方法中获取的事件,并使用视图状态的适当协调器重复循环。

然而,问题是在发布该事件时,什么都没有发生。start()没有显示它收到了事件,coordinateToRoot也没有被再次调用。

我在下面创建了最基本的版本来演示这一点。我还硬编码showStartScene返回.signedIn而不是查找身份验证状态。

在下面的示例中,我希望一旦加载视图,presenter.signal应该立即发出一个导致打印语句显示的事件。

会话状态

enum SessionState: String {
    case unknown, signedIn, signedOut
}

应用协调器

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 }
    }
}

启动协调器

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
    }
}

编辑 我添加了一些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

标签: swiftrx-swiftcoordinator-patternpublishsubject

解决方案


coordinateToRoot与 . 返回的 Observable 的生命周期无关AppCoordinator.start(_:)。这意味着无法保证订阅coordinateToRootStartPresenter订阅的顺序。

为了保证顺序,我认为您可以使用运算符并为参数do传递一个闭包。onSubscribe这个onSubscribe闭包将在订阅底层 observable 之前运行。

这是我认为你可以做出的改变:

final class AppCoordinator: BaseCoordinator<Void> {

    override func start() -> Observable<Void> {
        return Observable<Void>.never().do(onSubscribe: { [weak self] _ in
             self?.coordinateToRoot(basedOn: .unknown)
        })
    }
}


推荐阅读