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

问题描述 投票:2回答:2

我正在努力将我的一些View模型移植到(粗略的)有限状态机中,因为我的UI倾向于很好地适应该模式(Mealy / Moore,不关心这个问题的目的)。此外,当完成状态良好 - 状态机真正清理测试 - 因为它们禁止某些测试排列发生。

我当前的视图模型使用RxSwift(和RxKotlin - 取决于应用程序),底层用例(数据库调用,网络调用等)也使用Rx(因此我需要留在该生态系统中)。

我发现Rx很棒,状态机很棒 - > Rx +状态机看起来有点像哈希做任何不平凡的事情。例如,我知道我可以使用.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函数返回的是Observable(如网络调用或需要很长时间的东西,已经在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操作符接受一个Observable累加器,但是这段代码的结果是状态机订阅或运行了太多次。我猜是因为它在正在积累的observable中的每个状态上运行。

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中从状态转换的结果更新...这仍然感觉它可能导致并发问题,但不知道还有什么工作。

swift kotlin rx-java state-machine rx-swift
2个回答
1
投票

不,您不必完全同步以维持任意复杂状态。是的,如果没有scan,有办法实现所需的行为。怎么样的withLatestFrom,其中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 ...
  })
);

请注意,更复杂的案例(如竞争条件有风险)可能需要至少与combineLatest和approp一样复杂的事情。 Scheduler到位了。


1
投票

我认为Elm的系统在这里可以派上用场。在Elm中,你传递给系统的reducer不仅仅返回状态,它还返回一个“命令”,在我们的例子中它将是一个Observable<Event>(不是RxSwift.Event,而是你的事件枚举。)这个命令不是' t存储在扫描状态,但是它在扫描之外订阅并且其输出被反馈到扫描中(通过某种类型的主题。)需要取消的任务将观察当前状态并基于启动和停止操作国家。

RxSwift生态系统中有几个库可以帮助简化这些事情。两个主要的是,ReactorKitRxFeedback。还有其他几个......

有关我所说的简单示例,请查看this gist。这种系统允许您的Moore机器在进入可能导致0..n新输入事件的状态时触发动作。

© www.soinside.com 2019 - 2024. All rights reserved.