我有一个使用MVP
和Coordinator
模式的应用。
当子协调员发送事件时,我希望AppCoordinator
递归调用一个方法,该方法基于某个SessionState
选择下一个协调器。
应用程序的基本流程如下-
AppCoordinator
start()
以初始状态调用coordinateToRoot
showStartScene()
,这将启动子协调器 StartCoordinator
start()
创建MVP
模块,该模块现在对用户可见MVP
模块调用AuthSvc
,该模块对iDP进行异步调用并确认身份验证状态AppCoordinator
的{{1}}方法中的订阅获取,然后使用适当的视图状态协调器重复该循环。但是,问题在于该事件的发布没有任何反应。 coordinateToRoot
未显示它已接收到事件,并且start()
也未再次调用。
我已经创建了最基本的版本,可以在下面进行演示。我还对coordinateToRoot
进行了硬编码以返回showStartScene
,而不是查找auth状态。
在下面的示例中,我希望一旦加载视图,.signedIn
应该立即发出一个事件,该事件导致显示一条打印语句。
SessionState
presenter.signal
AppCoordinator
enum SessionState: String {
case unknown, signedIn, signedOut
}
StartCoordinator
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 }
}
}
启动MVP模块
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
}
}
有趣的是,如果我在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
进行订阅?
答案 0 :(得分:0)
coordinateToRoot
与 AppCoordinator.start(_:)
返回的 Observable 的生命周期无关。这意味着无法保证订阅 coordinateToRoot
和 StartPresenter
的顺序。
为了保证顺序,我认为您可以使用 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)
})
}
}