ReactiveAutomaton

by inamiy

🤖 ReactiveCocoa + State Machine, inspired by Redux and Elm.

202 Stars 5 Forks Last release: almost 4 years ago (0.2.0) MIT License 82 Commits 5 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

ReactiveAutomaton

ReactiveCocoa + State Machine, inspired by Redux and Elm. A successor of SwiftState.

Example

(Demo app is available at ReactiveCocoaCatalog)

To make a state transition diagram like above with additional effects, follow these steps:

// 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 (SignalProducers) 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.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.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)") }

And let's test!

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.

Note that any sizes of

State
and
Input
will work using
ReactiveAutomaton
, from single state (like above example) to covering whole app's states (like React.js + Redux architecture).

References

  1. iOSDC 2016 (Tokyo, in Japanese) (2016/08/20)
  2. iOSConf SG (Singapore, in English) (2016/10/20-21)

License

MIT

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.