我正在努力将我的某些视图模型移植到(粗糙的)有限状态机器中,因为我的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新输入事件的状态时开发动作。