SwiftState 6.0.0

SwiftState 6.0.0

Tests测试
Lang语言 SwiftSwift
许可证 MIT
Released最新发布2019年6月
SPM支持 SPM

Yasuhiro Inami 维护。



  • Yasuhiro Inami

SwiftState

Swift 中优雅的状态机。

SwiftState

示例

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)

这将打印

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

通过事件进行状态转换

使用 <-! 操作符尝试通过 Event 进行状态转换,而不是指定目标 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")

如果没有基于 Event 的转换,使用内建的 NoEvent

具有关联值的 State & Event 枚举

上面的例子使用的是易于理解的 箭头式路由,但它缺少处理 具有关联值的 State & Event 枚举 的能力。在这种情况下,可以使用以下任一函数应用 闭包式路由

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

例如:

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"))

其行为与 JavaScript 的安全状态容器类似 rackt/Redux,其中 RouteMapping 可以理解为 Redux.Reducer

请参阅 XCTest 用例以获取更多示例。

功能

  • 简单的 Swift 语法
    • 转换: .state0 => .state1[.state0, .state1] => .state2
    • 尝试状态: machine <- .state1
    • 尝试状态加消息: machine <- (.state1, "GoGoGo")
    • 尝试事件: machine <-! .event1
  • 高度灵活的转换路由
    • 使用 Condition

    • 使用 .any 状态

      • 入口处理: .any => .someState
      • 出口处理: .someState => .any
      • 黑名单: .any => .any + Condition
    • 使用 .any 事件

    • 路由映射(基于闭包的路由): #36

  • 使用 order: UInt8 的成功/错误处理(比之前/之后处理器更灵活)
  • 使用 Disposable 删除路由和处理程序
  • 路由链: .state0 => .state1 => .state2
  • 分层状态机: #10

术语

术语 类型 描述
状态 StateType(协议) 主要枚举,描述每个状态,例如 .state0
事件 EventType(协议) 路由组的名称。转换可以通过 Event 触发,而不是显式地针对下一个 State
状态机 Machine 状态转换管理器,可以独立为各种转换注册 Route/RouteMappingHandler
转换 转换 From- 状态和 to- 状态表示为 .state1 => .state2。同样,可以使用 .any 表示 任何状态
路由 路由 Transition + Condition
条件 上下文 -> Bool 验证转换的闭包。如果条件返回 false,则转换将失败,并且不会调用相关处理程序。
路由映射 (event: E?, fromState: S, userInfo: Any?) -> S? 定义路由的另一种方式是使用闭包而不是转换箭头(=>)。当状态和事件都是具有关联值的枚举时,这非常有用。返回值(S?)表示首选的 toState,其中传递 nil 表示没有可用路由。有关更多信息,请参阅 #36
状态路由映射 (fromState: S, userInfo: Any?) -> [S]? 使用闭包而非转换箭头(=>)定义路由的另一种方式。当状态是具有关联值的枚举时,这很有用。返回值([S]?)表示从单个 fromState 可能有多个 toState(例如,多个路由的简称 .state0 => [.state1, .state2])。更多信息请参阅 #36
处理程序 上下文 -> Void 当状态成功更改时调用的转换回调。
上下文 (事件: E?,从状态: S,到状态: S,用户信息: Any?) 闭包参数用于 ConditionHandler
TransitionChain / RouteChain 表示为 .state1 => .state2 => .state3 的连续路由组。

相关文章

  1. Swift中创建有限自动机(状态机) - Qiita(日语)
  2. 使用Swift+有限自动机扩展Promise - Qiita(日语)

许可

MIT