如何在没有扫描功能的情况下将状态保留在RX中



我正在努力将我的某些视图模型移植到(粗糙的)有限状态机器中,因为我的UI倾向于适合该模式(Mealy/Moore,不在乎这个问题的目的)。此外,当做得好时 - 州机器确实清理了测试 - 它们禁止某些测试排列发生。

我的当前视图模型使用rxswift(和rxkotlin-取决于应用程序),以及基础用例(数据库呼叫,网络调用等)也使用RX(因此为什么我需要留在该生态系统中)。p>我发现的是RX很棒,State Machines很棒 -> RX State Machines似乎有点像做任何不平凡的事情。例如,我知道我可以使用.scan操作员保留某些状态,如果我的状态机完全同步(例如,在Swift中大致类似的情况):

enum Event {
    case event1
    case event2
    case event3
}
enum State {
    case state1
    case state2
    case state3
    func on(event: Event) -> State {
        switch (self, event) {
        case (.state1, .event1):
            // Do something
            return .state2
        case (.state2, .event2):
            // Do something
            return .state3
        default:
            return self // (or nil, or something)
        }
    }
}
func foo() -> Observable<State> {
    let events = Observable<Event>.of(.event1, .event2, .event3)
    return events.scan(State.state1) { (currentState, event) -> State in
        return currentState.on(event)
    }
}

但是,如果我的State.on函数的返回是可观察的(例如网络调用或需要很长时间的东西,已经在RX中)?

我该怎么办?
enum State {
    case notLoggedIn
    case loggingIn
    case loggedIn
    case error
    func on(event: Event) -> Observable<State> {
        switch (self, event) {
        case (.notLoggedIn, .event1):
            return api.login(credentials)
                .map({ (isLoggedIn) -> State in
                    if isLoggedIn {
                        return .loggedIn
                    }
                    return .error
                })
                .startWith(.loggingIn)
        ... other code ...
        default:
            return self
        }
    }
}

我已经尝试使.scan操作员使用可观察的累加器,但是此代码的结果是状态机被订阅或运行了太多次。我猜是因为它在可观察到的正在积累的可观察到的状态下运行。

return events.scan(Observable.just(State.state1)) { (currentState, event) -> Observable<State> in
    currentState.flatMap({ (innerState) -> Observable<State> in
        return innerState.on(event: event)
    })
}.flatMap { (states) -> Observable<State> in
    return states
}

我认为,如果我可以清楚地将state变量重新添加回去,那么最简单的实现可能看起来像:

return events.flatMapLatest({ (event) -> Observable<State> in
    return self.state.on(event: event)
        .do(onNext: { (state) in
            self.state = state
        })
})

但是,从私有状态变量将其提取到可观察到的流,然后对其进行更新 - 嗯,不仅丑陋,我觉得我只是在等待被并发错误击中。


编辑:基于Sereja Bogolubov的反馈 - 我添加了一个继电器并提出了此代码 - 仍然不是很好,但是到达那里。

let relay = BehaviorRelay<State>(value: .initial)
...
func transition(from state: State, on event: Event) -> Observable<State> {
    switch (state, event) {
    case (.notLoggedIn, .event1):
        return api.login(credentials)
            .map({ (isLoggedIn) -> State in
                if isLoggedIn {
                    return .loggedIn
                }
                return .error
            })
            .startWith(.loggingIn)
    ... other code ...
    default:
        return self
    }
}
return events.withLatestFrom(relay.asObservable(), resultSelector: { (event, state) -> Observable<State> in
    return self.transition(from: state, on: event)
        .do(onNext: { (state) in
            self.relay.accept(state)
        })
}).flatMap({ (states) -> Observable<State> in
    return states
})

从状态过渡的结果中,在doOnNext中更新了继电器(或重播主题或其他主题)……这仍然可能会导致并发问题,但不确定还有什么作用。

不,您不必完全同步即可维持任意复杂状态。是的,没有scan实现所需行为的方法。如何使用 other是您当前状态(即单独的 Observable<MyState>,但是在引擎盖下需要 ReplaySubject<MyState>)。

)。

让我知道您是否需要更多详细信息。


概念证明,JavaScript:

const source = range(0, 10);
const state = new ReplaySubject(1);
const example = source.pipe(
  withLatestFrom(state), // that's the way you read actual state
  map(([n, currentState]) => {
    state.next(n); // that's the way you change the state
    return ...
  })
);

请注意,更复杂的案例(例如种族条件冒险)可能至少需要像combinateast和适当的同样复杂的东西。 Scheduler的到位。

我认为榆树的系统可以在这里派上用场。在ELM中,您进入系统的还原器不仅返回状态,还返回一个"命令",在我们的情况下,该命令是Observable<Event>(不是rxswift.event,而是您的事件枚举。)此命令ISN ISN't存储在扫描状态中,而是将其订阅到扫描外部,其输出被反馈到扫描中(通过某种主题。状态。

RXSWIFT生态系统中有几个库有助于简化此类内容。这两个主要的是Reactorkit和rxFeedback。还有其他几个...

有关我在说什么的简单示例,请查看此要旨。这种系统允许您的摩尔机器在进入可能导致0..n新输入事件的状态时开发动作。

最新更新