ReactiveAutomaton 0.5.0

ReactiveAutomaton 0.5.0

Yasuhiro Inami维护。



  • Yasuhiro Inami

ReactiveAutomaton

ReactiveCocoa + 状态机,受ReduxElm的启发。是SwiftState的继任者。

示例

(演示应用可在ReactiveCocoaCatalog找到)

要创建类似于上面的带有额外效果的 状态转换图,请按照以下步骤进行

// 1. Define `State`s and `Input`s.
enum State {
    case loggedOut, loggingIn, loggedIn, loggingOut
}

enum Input {
    case login, loginOK, logout, logoutOK
    case forceLogout
}

// Additional effects (`SignalProducer`s) while state-transitioning.
// (NOTE: Use `SignalProducer.empty` for no effect)
let loginOKProducer = /* show UI, setup DB, request APIs, ..., and send `Input.loginOK` */
let logoutOKProducer = /* show UI, clear cache, cancel APIs, ..., and send `Input.logoutOK` */
let forceLogoutOKProducer = /* do something more special, ..., and send `Input.logoutOK` */

let canForceLogout: (State) -> Bool = [.loggingIn, .loggedIn].contains

// 2. Setup state-transition mappings.
let mappings: [Automaton<State, Input>.EffectMapping] = [

  /*  Input   |   fromState => toState     |      Effect       */
  /* ----------------------------------------------------------*/
    .login    | .loggedOut  => .loggingIn  | loginOKProducer,
    .loginOK  | .loggingIn  => .loggedIn   | .empty,
    .logout   | .loggedIn   => .loggingOut | logoutOKProducer,
    .logoutOK | .loggingOut => .loggedOut  | .empty,

    .forceLogout | canForceLogout => .loggingOut | forceLogoutOKProducer
]

// 3. Prepare input pipe for sending `Input` to `Automaton`.
let (inputSignal, inputObserver) = Signal<Input, NoError>.pipe()

// 4. Setup `Automaton`.
let automaton = Automaton(
    state: .loggedOut,
    input: inputSignal,
    mapping: reduce(mappings),  // combine mappings using `reduce` helper
    strategy: .latest   // NOTE: `.latest` cancels previous running effect
)

// Observe state-transition replies (`.success` or `.failure`).
automaton.replies.observeNext { reply in
    print("received reply = \(reply)")
}

// Observe current state changes.
automaton.state.producer.startWithValues { state in
    print("current state = \(state)")
}

然后进行测试!

let send = inputObserver.send(value:)

expect(automaton.state.value) == .loggedIn    // already logged in
send(Input.logout)
expect(automaton.state.value) == .loggingOut  // logging out...
// `logoutOKProducer` will automatically send `Input.logoutOK` later
// and transit to `State.loggedOut`.

expect(automaton.state.value) == .loggedOut   // already logged out
send(Input.login)
expect(automaton.state.value) == .loggingIn   // logging in...
// `loginOKProducer` will automatically send `Input.loginOK` later
// and transit to `State.loggedIn`.

// 👨🏽 < But wait, there's more!
// Let's send `Input.forceLogout` immediately after `State.loggingIn`.

send(Input.forceLogout)                       // 💥💣💥
expect(automaton.state.value) == .loggingOut  // logging out...
// `forceLogoutOKProducer` will automatically send `Input.logoutOK` later
// and transit to `State.loggedOut`.

请注意,使用ReactiveAutomaton的任意大小的StateInput都将适用,从单个状态(如上面的示例)到覆盖整个应用程序的状态(如React.js + Redux架构)均可。

参考资料

  1. iOSDC 2016(东京,日语) (2016/08/20)
  2. iOSConf SG(新加坡,英语) (2016/10/20-21)

许可

MIT