SwiftState
Swift 中优雅的状态机。
示例
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 。 |
状态机 |
|
状态转换管理器,可以独立为各种转换注册 Route /RouteMapping 和 Handler 。 |
转换 | 转换 |
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?) |
闭包参数用于 Condition 和 Handler 。 |
链 | TransitionChain / RouteChain |
表示为 .state1 => .state2 => .state3 的连续路由组。 |