RxSwift Subject не запускает событие при вызове
У меня есть приложение, использующее MVP
с Coordinator
шаблон.
Когда детский координатор отправляет событие, я ожидаю, что AppCoordinator
рекурсивно вызвать метод, который выбирает следующего координатора на основе некоторого SessionState
.
Основной поток приложения выглядит следующим образом:
AppCoordinator
start()
призываетcoordinateToRoot
с начальным состоянием- Подписывается на
showStartScene()
который запускает дочерний координатор
StartCoordinator
start()
создаетMVP
модуль, который теперь виден пользователюMVP
модуль вызываетAuthSvc
который выполняет асинхронный вызов iDP и подтверждает состояние аутентификации- По завершении этой задачи публикует событие, которое регистрируется подпиской в
AppCoordinator
сcoordinateToRoot
метод, и цикл повторяется с использованием соответствующего координатора для состояния просмотра.
Проблема, однако, в том, что при публикации этого события ничего не происходит. start()
не показывает, что получил событие и coordinateToRoot
больше не вызывается.
Я создал самую простую версию, которую могу ниже, чтобы продемонстрировать это. Я также жестко запрограммировалshowStartScene
возвращать .signedIn
а не поиск состояния аутентификации.
В приведенном ниже примере я ожидал, что после загрузки представления presenter.signal
должен немедленно вызвать событие, вызывающее отображение оператора печати.
SessionState
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 }
}
}
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
}
}
ИЗМЕНИТЬ Я добавил несколько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
создано?
1 ответ
не связан с жизненным циклом наблюдаемого, возвращаемого
AppCoordinator.start(_:)
. Это означает, что нет никакой гарантии, в каком порядке
coordinateToRoot
а также
StartPresenter
подписаны на.
Чтобы гарантировать заказ, я думаю, вы можете использовать
do
оператор и передайте закрытие аргумента. Этот
onSubscribe
закрытие будет выполняться до подписки на базовый наблюдаемый объект.
Вот изменения, которые, я думаю, вы могли бы внести:
final class AppCoordinator: BaseCoordinator<Void> {
override func start() -> Observable<Void> {
return Observable<Void>.never().do(onSubscribe: { [weak self] _ in
self?.coordinateToRoot(basedOn: .unknown)
})
}
}