Git Product home page Git Product logo

swiftstate's Introduction

SwiftState Circle CI

Elegant state machine for Swift.

SwiftState

Example

enum MyState: StateType {
    case State0, State1, State2
}
// setup state machine
let machine = StateMachine<MyState, NoEvent>(state: .State0) { machine in
    
    machine.addRoute(.State0 => .State1)
    machine.addRoute(.Any => .State2) { context in print("Any => 2, msg=\(context.userInfo)") }
    machine.addRoute(.State2 => .Any) { context in print("2 => Any, msg=\(context.userInfo)") }
    
    // add handler (`context = (event, fromState, toState, userInfo)`)
    machine.addHandler(.State0 => .State1) { context in
        print("0 => 1")
    }
    
    // add errorHandler
    machine.addErrorHandler { event, fromState, toState, userInfo in
        print("[ERROR] \(fromState) => \(toState)")
    }
}

// initial
XCTAssertEqual(machine.state, MyState.State0)

// tryState 0 => 1 => 2 => 1 => 0

machine <- .State1
XCTAssertEqual(machine.state, MyState.State1)

machine <- (.State2, "Hello")
XCTAssertEqual(machine.state, MyState.State2)

machine <- (.State1, "Bye")
XCTAssertEqual(machine.state, MyState.State1)

machine <- .State0  // fail: no 1 => 0
XCTAssertEqual(machine.state, MyState.State1)

This will print:

0 => 1
Any => 2, msg=Optional("Hello")
2 => Any, msg=Optional("Bye")
[ERROR] State1 => State0

Transition by Event

Use <-! operator to try transition by Event rather than specifying target State.

enum MyEvent: EventType {
    case Event0, Event1
}
let machine = StateMachine<MyState, MyEvent>(state: .State0) { machine in
    
    // add 0 => 1 => 2
    machine.addRoutes(event: .Event0, transitions: [
        .State0 => .State1,
        .State1 => .State2,
    ])
    
    // add event handler
    machine.addHandler(event: .Event0) { context in
        print(".Event0 triggered!")
    }
}

// initial
XCTAssertEqual(machine.state, MyState.State0)

// tryEvent
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State1)

// tryEvent
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2)

// tryEvent (fails)
machine <-! .Event0
XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any")

If there is no Event-based transition, use built-in NoEvent instead.

State & Event enums with associated values

Above examples use arrow-style routing which are easy to understand, but it lacks in ability to handle state & event enums with associated values. In such cases, use either of the following functions to apply closure-style routing:

  • machine.addRouteMapping(routeMapping)
    • RouteMapping: (event: E?, fromState: S, userInfo: Any?) -> S?
  • machine.addStateRouteMapping(stateRouteMapping)
    • StateRouteMapping: (fromState: S, userInfo: Any?) -> [S]?

For example:

enum StrState: StateType {
    case Str(String) ...
}
enum StrEvent: EventType {
    case Str(String) ...
}

let machine = Machine<StrState, StrEvent>(state: .Str("initial")) { machine in
    
    machine.addRouteMapping { event, fromState, userInfo -> StrState? in
        // no route for no-event
        guard let event = event else { return nil }
        
        switch (event, fromState) {
            case (.Str("gogogo"), .Str("initial")):
                return .Str("Phase 1")
            case (.Str("gogogo"), .Str("Phase 1")):
                return .Str("Phase 2")
            case (.Str("finish"), .Str("Phase 2")):
                return .Str("end")
            default:
                return nil
        }
    }
    
}

// initial
XCTAssertEqual(machine.state, StrState.Str("initial"))

// tryEvent (fails)
machine <-! .Str("go?")
XCTAssertEqual(machine.state, StrState.Str("initial"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"))

// tryEvent (fails)
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("Phase 1"), "No change.")

// tryEvent
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"))

// tryEvent (fails)
machine <-! .Str("gogogo")
XCTAssertEqual(machine.state, StrState.Str("Phase 2"), "No change.")

// tryEvent
machine <-! .Str("finish")
XCTAssertEqual(machine.state, StrState.Str("end"))

This behaves very similar to JavaScript's safe state-container rackt/Redux, where RouteMapping can be interpretted as Redux.Reducer.

For more examples, please see XCTest cases.

Features

  • Easy Swift syntax
    • Transition: .State0 => .State1, [.State0, .State1] => .State2
    • Try state: machine <- .State1
    • Try state + messaging: machine <- (.State1, "GoGoGo")
    • Try event: machine <-! .Event1
  • Highly flexible transition routing
    • Using Condition

    • Using .Any state

      • Entry handling: .Any => .SomeState
      • Exit handling: .SomeState => .Any
      • Blacklisting: .Any => .Any + Condition
    • Using .Any event

    • Route Mapping (closure-based routing): #36

  • Success/Error handlers with order: UInt8 (more flexible than before/after handlers)
  • Removable routes and handlers using Disposable
  • Route Chaining: .State0 => .State1 => .State2
  • Hierarchical State Machine: #10

Terms

Term Type Description
State StateType (protocol) Mostly enum, describing each state e.g. .State0.
Event EventType (protocol) Name for route-group. Transition can be fired via Event instead of explicitly targeting next State.
State Machine Machine State transition manager which can register Route/RouteMapping and Handler separately for variety of transitions.
Transition Transition From- and to- states represented as .State1 => .State2. Also, .Any can be used to represent any state.
Route Route Transition + Condition.
Condition Context -> Bool Closure for validating transition. If condition returns false, transition will fail and associated handlers will not be invoked.
Route Mapping (event: E?, fromState: S, userInfo: Any?) -> S? Another way of defining routes using closure instead of transition arrows (=>). This is useful when state & event are enum with associated values. Return value (S?) means preferred-toState, where passing nil means no routes available. See #36 for more info.
State Route Mapping (fromState: S, userInfo: Any?) -> [S]? Another way of defining routes using closure instead of transition arrows (=>). This is useful when state is enum with associated values. Return value ([S]?) means multiple toStates from single fromState (synonym for multiple routing e.g. .State0 => [.State1, .State2]). See #36 for more info.
Handler Context -> Void Transition callback invoked when state has been changed successfully.
Context (event: E?, fromState: S, toState: S, userInfo: Any?) Closure argument for Condition & Handler.
Chain TransitionChain / RouteChain Group of continuous routes represented as .State1 => .State2 => .State3

Related Articles

  1. Swiftで有限オートマトン(ステートマシン)を作る - Qiita (Japanese)
  2. Swift+有限オートマトンでPromiseを拡張する - Qiita (Japanese)

Licence

MIT

swiftstate's People

Contributors

inamiy avatar adamyanalunas avatar ileitch avatar nwest avatar rhummelmose avatar siyusong avatar readmecritic avatar

Watchers

James Cloos avatar rosa maria palacios juncosa avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.