diff --git a/.gitignore b/.gitignore index 8179b60..c623c4f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,5 @@ DerivedData *.xcuserstate Carthage/Build +.build +Packages/ \ No newline at end of file diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..b86ac08 --- /dev/null +++ b/Package.swift @@ -0,0 +1,13 @@ +// +// Package.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +import PackageDescription + +let package = Package( + name: "SwiftState" +) \ No newline at end of file diff --git a/README.md b/README.md index 050cbb2..9275388 100644 --- a/README.md +++ b/README.md @@ -74,7 +74,7 @@ enum MyEvent: EventType { let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoute(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -89,9 +89,8 @@ machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2) // tryEvent -let success = machine <-! .Event0 -XCTAssertEqual(machine.state, MyState.State2) -XCTAssertFalse(success, "Event0 doesn't have 2 => Any") +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. @@ -126,8 +125,9 @@ State Machine | `Machine` | State transition manager which c 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](https://github.com/ReactKit/SwiftState/pull/36) for more info. -Handler | `Context -> Void` | Transition callback invoked after state has been changed. +Event 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](https://github.com/ReactKit/SwiftState/pull/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 `toState`s from single `fromState`. See [#36](https://github.com/ReactKit/SwiftState/pull/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` diff --git a/Sources/Disposable.swift b/Sources/Disposable.swift new file mode 100644 index 0000000..6bd3be7 --- /dev/null +++ b/Sources/Disposable.swift @@ -0,0 +1,45 @@ +// +// Disposable.swift +// ReactiveCocoa +// +// Created by Justin Spahr-Summers on 2014-06-02. +// Copyright (c) 2014 GitHub. All rights reserved. +// + +// +// NOTE: +// This file is a partial copy from ReactiveCocoa v4.0.0-alpha.4 (removing `Atomic` dependency), +// which has not been taken out as microframework yet. +// https://github.com/ReactiveCocoa/ReactiveCocoa/issues/2579 +// +// Note that `ActionDisposable` also works as `() -> ()` wrapper to help suppressing warning: +// "Expression resolved to unused function", when returned function was not used. +// + +/// Represents something that can be “disposed,” usually associated with freeing +/// resources or canceling work. +public protocol Disposable { + /// Whether this disposable has been disposed already. + var disposed: Bool { get } + + func dispose() +} + +/// A disposable that will run an action upon disposal. +public final class ActionDisposable: Disposable { + private var action: (() -> ())? + + public var disposed: Bool { + return action == nil + } + + /// Initializes the disposable to run the given action upon disposal. + public init(action: () -> ()) { + self.action = action + } + + public func dispose() { + self.action?() + self.action = nil + } +} \ No newline at end of file diff --git a/Sources/EventType.swift b/Sources/EventType.swift new file mode 100644 index 0000000..9303ef1 --- /dev/null +++ b/Sources/EventType.swift @@ -0,0 +1,62 @@ +// +// EventType.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/05. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +public protocol EventType: Hashable {} + +// MARK: Event + +/// `EventType` wrapper for handling`.Any` event. +public enum Event: Hashable +{ + case Some(E) + case Any + + public var value: E? + { + switch self { + case .Some(let x): return x + default: return nil + } + } + + public var hashValue: Int + { + switch self { + case .Some(let x): return x.hashValue + case .Any: return _hashValueForAny + } + } +} + +public func == (lhs: Event, rhs: Event) -> Bool +{ + switch (lhs, rhs) { + case let (.Some(x1), .Some(x2)) where x1 == x2: + return true + case (.Any, .Any): + return true + default: + return false + } +} + +// MARK: NoEvent + +/// Useful for creating StateMachine without events, i.e. `StateMachine`. +public enum NoEvent: EventType +{ + public var hashValue: Int + { + return 0 + } +} + +public func == (lhs: NoEvent, rhs: NoEvent) -> Bool +{ + return true +} diff --git a/SwiftState/Info.plist b/Sources/Info.plist similarity index 100% rename from SwiftState/Info.plist rename to Sources/Info.plist diff --git a/Sources/Machine.swift b/Sources/Machine.swift new file mode 100644 index 0000000..c379673 --- /dev/null +++ b/Sources/Machine.swift @@ -0,0 +1,581 @@ +// +// Machine.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +/// +/// State-machine which can `tryEvent()` (event-driven). +/// +/// This is a superclass (simpler version) of `StateMachine` that doesn't allow `tryState()` (direct state change). +/// +/// This class can be used as a safe state-container in similar way as [rackt/Redux](https://github.com/rackt/redux), +/// where `EventRouteMapping` can be interpretted as `Redux.Reducer`. +/// +public class Machine +{ + /// Closure argument for `Condition` & `Handler`. + public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) + + /// Closure for validating transition. + /// If condition returns `false`, transition will fail and associated handlers will not be invoked. + public typealias Condition = Context -> Bool + + /// Transition callback invoked when state has been changed successfully. + public typealias Handler = Context -> () + + /// Closure-based route, mainly for `tryEvent()` (and also works for subclass's `tryState()`). + /// - Returns: Preferred `toState`. + public typealias EventRouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? + + internal typealias _RouteDict = [Transition : [String : Condition?]] + + private lazy var _routes: [Event : _RouteDict] = [:] + private lazy var _routeMappings: [String : EventRouteMapping] = [:] + + /// `tryEvent()`-based handler collection. + private lazy var _handlers: [Event : [_HandlerInfo]] = [:] + + internal lazy var _errorHandlers: [_HandlerInfo] = [] + + internal var _state: S + + //-------------------------------------------------- + // MARK: - Init + //-------------------------------------------------- + + public init(state: S, initClosure: (Machine -> ())? = nil) + { + self._state = state + + initClosure?(self) + } + + public func configure(closure: Machine -> ()) + { + closure(self) + } + + public var state: S + { + return self._state + } + + //-------------------------------------------------- + // MARK: - hasRoute + //-------------------------------------------------- + + /// Check for added routes & routeMappings. + public func hasRoute(event event: E, transition: Transition, userInfo: Any? = nil) -> Bool + { + guard let fromState = transition.fromState.value, + toState = transition.toState.value else + { + assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") + return false + } + + return self.hasRoute(event: event, fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for added routes & routeMappings. + public func hasRoute(event event: E, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + return self._hasRoute(event: event, fromState: fromState, toState: toState, userInfo: userInfo) + } + + internal func _hasRoute(event event: E?, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + if self._hasRouteInDict(event: event, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + + if self._hasRouteMappingInDict(event: event, fromState: fromState, toState: .Some(toState), userInfo: userInfo) != nil { + return true + } + + return false + } + + /// Check for `_routes`. + private func _hasRouteInDict(event event: E?, fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + + var routeDicts: [_RouteDict] = [] + + if let event = event { + for (ev, routeDict) in self._routes { + if ev.value == event || ev == .Any { + routeDicts += [routeDict] + } + } + } + else { + // + // NOTE: + // If `event` is `nil`, it means state-based-transition, + // and all registered event-based-routes will be examined. + // + routeDicts += self._routes.values.lazy + } + + for routeDict in routeDicts { + if let keyConditionDict = routeDict[validTransition] { + for (_, condition) in keyConditionDict { + if _canPassCondition(condition, forEvent: event, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + } + } + } + } + + return false + } + + /// Check for `_routeMappings`. + private func _hasRouteMappingInDict(event event: E?, fromState: S, toState: S?, userInfo: Any? = nil) -> S? + { + for mapping in self._routeMappings.values { + if let preferredToState = mapping(event: event, fromState: fromState, userInfo: userInfo) + where preferredToState == toState || toState == nil + { + return preferredToState + } + } + + return nil + } + + //-------------------------------------------------- + // MARK: - tryEvent + //-------------------------------------------------- + + /// - Returns: Preferred-`toState`. + public func canTryEvent(event: E, userInfo: Any? = nil) -> S? + { + // check for `_routes` + for case let routeDict? in [self._routes[.Some(event)], self._routes[.Any]] { + for (transition, keyConditionDict) in routeDict { + if transition.fromState == .Some(self.state) || transition.fromState == .Any { + for (_, condition) in keyConditionDict { + // if toState is `.Any`, always treat as identity transition + let toState = transition.toState.value ?? self.state + + if _canPassCondition(condition, forEvent: event, fromState: self.state, toState: toState, userInfo: userInfo) { + return toState + } + } + } + } + } + + // check for `_routeMappings` + if let toState = _hasRouteMappingInDict(event: event, fromState: self.state, toState: nil, userInfo: userInfo) { + return toState + } + + return nil + } + + public func tryEvent(event: E, userInfo: Any? = nil) -> Bool + { + let fromState = self.state + + if let toState = self.canTryEvent(event, userInfo: userInfo) { + + // collect valid handlers before updating state + let validHandlerInfos = self._validHandlerInfos(event: event, fromState: fromState, toState: toState) + + // update state + self._state = toState + + // perform validHandlers after updating state. + for handlerInfo in validHandlerInfos { + handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return true + } + else { + for handlerInfo in self._errorHandlers { + let toState = self.state // NOTE: there's no `toState` for failure of event-based-transition + handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return false + } + } + + private func _validHandlerInfos(event event: E, fromState: S, toState: S) -> [_HandlerInfo] + { + let validHandlerInfos = [ self._handlers[.Some(event)], self._handlers[.Any] ] + .filter { $0 != nil } + .map { $0! } + .flatten() + + return validHandlerInfos.sort { info1, info2 in + return info1.order < info2.order + } + } + + //-------------------------------------------------- + // MARK: - Route + //-------------------------------------------------- + + // MARK: addRoutes(event:) + + public func addRoutes(event event: E, transitions: [Transition], condition: Machine.Condition? = nil) -> Disposable + { + return self.addRoutes(event: .Some(event), transitions: transitions, condition: condition) + } + + public func addRoutes(event event: Event, transitions: [Transition], condition: Machine.Condition? = nil) -> Disposable + { + let routes = transitions.map { Route(transition: $0, condition: condition) } + return self.addRoutes(event: event, routes: routes) + } + + public func addRoutes(event event: E, routes: [Route]) -> Disposable + { + return self.addRoutes(event: .Some(event), routes: routes) + } + + public func addRoutes(event event: Event, routes: [Route]) -> Disposable + { + // NOTE: uses `map` with side-effects + let disposables = routes.map { self._addRoute(event: event, route: $0) } + + return ActionDisposable.init { + disposables.forEach { $0.dispose() } + } + } + + internal func _addRoute(event event: Event = .Any, route: Route) -> Disposable + { + let transition = route.transition + let condition = route.condition + + let key = _createUniqueString() + + if self._routes[event] == nil { + self._routes[event] = [:] + } + + var routeDict = self._routes[event]! + if routeDict[transition] == nil { + routeDict[transition] = [:] + } + + var keyConditionDict = routeDict[transition]! + keyConditionDict[key] = condition + routeDict[transition] = keyConditionDict + + self._routes[event] = routeDict + + let _routeID = _RouteID(event: event, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRoute(_routeID) + } + } + + // MARK: addRoutes(event:) + conditional handler + + public func addRoutes(event event: E, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> Disposable + { + return self.addRoutes(event: .Some(event), transitions: transitions, condition: condition, handler: handler) + } + + public func addRoutes(event event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> Disposable + { + let routeDisposable = self.addRoutes(event: event, transitions: transitions, condition: condition) + let handlerDisposable = self.addHandler(event: event, handler: handler) + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + public func addRoutes(event event: E, routes: [Route], handler: Handler) -> Disposable + { + return self.addRoutes(event: .Some(event), routes: routes, handler: handler) + } + + public func addRoutes(event event: Event, routes: [Route], handler: Handler) -> Disposable + { + let routeDisposable = self.addRoutes(event: event, routes: routes) + let handlerDisposable = self.addHandler(event: event, handler: handler) + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRoute + + private func _removeRoute(_routeID: _RouteID) -> Bool + { + guard let event = _routeID.event else { return false } + + let transition = _routeID.transition + + if let routeDict_ = self._routes[event] { + var routeDict = routeDict_ + + if let keyConditionDict_ = routeDict[transition] { + var keyConditionDict = keyConditionDict_ + + keyConditionDict[_routeID.key] = nil + if keyConditionDict.count > 0 { + routeDict[transition] = keyConditionDict + } + else { + routeDict[transition] = nil + } + } + + if routeDict.count > 0 { + self._routes[event] = routeDict + } + else { + self._routes[event] = nil + } + + return true + } + + return false + } + + //-------------------------------------------------- + // MARK: - EventRouteMapping + //-------------------------------------------------- + + // MARK: addRouteMapping + + public func addRouteMapping(routeMapping: EventRouteMapping) -> Disposable + { + let key = _createUniqueString() + + self._routeMappings[key] = routeMapping + + let routeMappingID = _RouteMappingID(key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRouteMapping(routeMappingID) + } + } + + // MARK: addRouteMapping + conditional handler + + public func addRouteMapping(routeMapping: EventRouteMapping, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + let routeDisposable = self.addRouteMapping(routeMapping) + + let handlerDisposable = self._addHandler(event: .Any, order: order) { context in + guard let event = context.event else { return } + + if self._hasRouteMappingInDict(event: event, fromState: context.fromState, toState: .Some(context.toState), userInfo: context.userInfo) != nil { + handler(context) + } + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRouteMapping + + private func _removeRouteMapping(routeMappingID: _RouteMappingID) -> Bool + { + if self._routeMappings[routeMappingID.key] != nil { + self._routeMappings[routeMappingID.key] = nil + return true + } + else { + return false + } + } + + //-------------------------------------------------- + // MARK: - Handler + //-------------------------------------------------- + + // MARK: addHandler(event:) + + public func addHandler(event event: E, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + return self.addHandler(event: .Some(event), order: order, handler: handler) + } + + public func addHandler(event event: Event, order: HandlerOrder = _defaultOrder, handler: Machine.Handler) -> Disposable + { + return self._addHandler(event: event, order: order) { context in + // skip if not event-based transition + guard let triggeredEvent = context.event else { + return + } + + if triggeredEvent == event.value || event == .Any { + handler(context) + } + } + } + + private func _addHandler(event event: Event, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + if self._handlers[event] == nil { + self._handlers[event] = [] + } + + let key = _createUniqueString() + + var handlerInfos = self._handlers[event]! + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) + + self._handlers[event] = handlerInfos + + let handlerID = _HandlerID(event: event, transition: .Any => .Any, key: key) // NOTE: use non-`nil` transition + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: addErrorHandler + + public func addErrorHandler(order order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + let key = _createUniqueString() + + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) + + let handlerID = _HandlerID(event: nil, transition: nil, key: key) // NOTE: use `nil` transition + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: removeHandler + + private func _removeHandler(handlerID: _HandlerID) -> Bool + { + if let event = handlerID.event { + if let handlerInfos_ = self._handlers[event] { + var handlerInfos = handlerInfos_ + + if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { + self._handlers[event] = handlerInfos + return true + } + } + } + // `transition = nil` means errorHandler + else if handlerID.transition == nil { + if _removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { + return true + } + return false + } + + return false + } + +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// MARK: `<-!` (tryEvent) + +infix operator <-! { associativity left } + +public func <-! (machine: Machine, event: E) -> Machine +{ + machine.tryEvent(event) + return machine +} + +public func <-! (machine: Machine, tuple: (E, Any?)) -> Machine +{ + machine.tryEvent(tuple.0, userInfo: tuple.1) + return machine +} + +//-------------------------------------------------- +// MARK: - HandlerOrder +//-------------------------------------------------- + +/// Precedence for registered handlers (higher number is called later). +public typealias HandlerOrder = UInt8 + +internal let _defaultOrder: HandlerOrder = 100 + +//-------------------------------------------------- +// MARK: - Internal +//-------------------------------------------------- + +// generate approx 126bit random string +internal func _createUniqueString() -> String +{ + var uniqueString: String = "" + for _ in 1...8 { + uniqueString += String(UnicodeScalar(_random(0xD800))) // 0xD800 = 55296 = 15.755bit + } + return uniqueString +} + +internal func _validTransitions(fromState fromState: S, toState: S) -> [Transition] +{ + return [ + fromState => toState, + fromState => .Any, + .Any => toState, + .Any => .Any + ] +} + +internal func _canPassCondition(condition: Machine.Condition?, forEvent event: E?, fromState: S, toState: S, userInfo: Any?) -> Bool +{ + return condition?((event, fromState, toState, userInfo)) ?? true +} + +internal func _insertHandlerIntoArray(inout handlerInfos: [_HandlerInfo], newHandlerInfo: _HandlerInfo) +{ + var index = handlerInfos.count + + for i in Array(0..(inout handlerInfos: [_HandlerInfo], removingHandlerID: _HandlerID) -> Bool +{ + for i in 0.. { public let transition: Transition diff --git a/SwiftState/RouteChain.swift b/Sources/RouteChain.swift similarity index 95% rename from SwiftState/RouteChain.swift rename to Sources/RouteChain.swift index 162967a..f2b6d57 100644 --- a/SwiftState/RouteChain.swift +++ b/Sources/RouteChain.swift @@ -6,6 +6,7 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // +/// Group of continuous `Route`s. public struct RouteChain { public private(set) var routes: [Route] diff --git a/Sources/StateMachine.swift b/Sources/StateMachine.swift new file mode 100644 index 0000000..c37a22c --- /dev/null +++ b/Sources/StateMachine.swift @@ -0,0 +1,511 @@ +// +// StateMachine.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +/// +/// State-machine which can `tryState()` (state-driven) as well as `tryEvent()` (event-driven). +/// +/// - Note: +/// Use `NoEvent` type to ignore event-handlings whenever necessary. +/// +public final class StateMachine: Machine +{ + /// Closure-based routes for `tryState()`. + /// - Returns: Multiple `toState`s from single `fromState`. + public typealias StateRouteMapping = (fromState: S, userInfo: Any?) -> [S]? + + private lazy var _routes: _RouteDict = [:] + private lazy var _routeMappings: [String : StateRouteMapping] = [:] // NOTE: `StateRouteMapping`, not `EventRouteMapping` + + /// `tryState()`-based handler collection. + private lazy var _handlers: [Transition : [_HandlerInfo]] = [:] + + //-------------------------------------------------- + // MARK: - Init + //-------------------------------------------------- + + public override init(state: S, initClosure: (StateMachine -> ())? = nil) + { + super.init(state: state, initClosure: { machine in + initClosure?(machine as! StateMachine) + return + }) + } + + public override func configure(closure: StateMachine -> ()) + { + closure(self) + } + + //-------------------------------------------------- + // MARK: - hasRoute + //-------------------------------------------------- + + /// Check for added routes & routeMappings. + /// - Note: This method also checks for event-based-routes. + public func hasRoute(transition: Transition, userInfo: Any? = nil) -> Bool + { + guard let fromState = transition.fromState.value, + toState = transition.toState.value else + { + assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") + return false + } + + return self.hasRoute(fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for added routes & routeMappings. + /// - Note: This method also checks for event-based-routes. + public func hasRoute(fromState fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + if self._hasRouteInDict(fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + + if self._hasRouteMappingInDict(fromState: fromState, toState: toState, userInfo: userInfo) != nil { + return true + } + + // look for all event-based-routes + return super._hasRoute(event: nil, fromState: fromState, toState: toState, userInfo: userInfo) + } + + /// Check for `_routes`. + private func _hasRouteInDict(fromState fromState: S, toState: S, userInfo: Any? = nil) -> Bool + { + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + + // check for `_routes + if let keyConditionDict = self._routes[validTransition] { + for (_, condition) in keyConditionDict { + if _canPassCondition(condition, forEvent: nil, fromState: fromState, toState: toState, userInfo: userInfo) { + return true + } + } + } + } + + return false + } + + /// Check for `_routeMappings`. + private func _hasRouteMappingInDict(fromState fromState: S, toState: S, userInfo: Any? = nil) -> S? + { + for mapping in self._routeMappings.values { + if let preferredToStates = mapping(fromState: fromState, userInfo: userInfo) { + return preferredToStates.contains(toState) ? toState : nil + } + } + + return nil + } + + //-------------------------------------------------- + // MARK: - tryState + //-------------------------------------------------- + + /// - Note: This method also checks for event-based-routes. + public func canTryState(toState: S, userInfo: Any? = nil) -> Bool + { + return self.hasRoute(fromState: self.state, toState: toState, userInfo: userInfo) + } + + /// - Note: This method also tries state-change for event-based-routes. + public func tryState(toState: S, userInfo: Any? = nil) -> Bool + { + let fromState = self.state + + if self.canTryState(toState, userInfo: userInfo) { + + // collect valid handlers before updating state + let validHandlerInfos = self._validHandlerInfos(fromState: fromState, toState: toState) + + // update state + self._state = toState + + // + // Perform validHandlers after updating state. + // + // NOTE: + // Instead of using before/after handlers as seen in many other StateMachine libraries, + // SwiftState uses `order` value to perform handlers in 'fine-grained' order, + // only after state has been updated. (Any problem?) + // + for handlerInfo in validHandlerInfos { + handlerInfo.handler(Context(event: nil, fromState: fromState, toState: toState, userInfo: userInfo)) + } + + return true + + } + else { + for handlerInfo in self._errorHandlers { + handlerInfo.handler(Context(event: nil, fromState: fromState, toState: toState, userInfo: userInfo)) + } + } + + return false + } + + private func _validHandlerInfos(fromState fromState: S, toState: S) -> [_HandlerInfo] + { + var validHandlerInfos: [_HandlerInfo] = [] + + let validTransitions = _validTransitions(fromState: fromState, toState: toState) + + for validTransition in validTransitions { + if let handlerInfos = self._handlers[validTransition] { + for handlerInfo in handlerInfos { + validHandlerInfos += [handlerInfo] + } + } + } + + validHandlerInfos.sortInPlace { info1, info2 in + return info1.order < info2.order + } + + return validHandlerInfos + } + + //-------------------------------------------------- + // MARK: - Route + //-------------------------------------------------- + + // MARK: addRoute (no-event) + + public func addRoute(transition: Transition, condition: Condition? = nil) -> Disposable + { + let route = Route(transition: transition, condition: condition) + return self.addRoute(route) + } + + public func addRoute(route: Route) -> Disposable + { + let transition = route.transition + let condition = route.condition + + if self._routes[transition] == nil { + self._routes[transition] = [:] + } + + let key = _createUniqueString() + + var keyConditionDict = self._routes[transition]! + keyConditionDict[key] = condition + self._routes[transition] = keyConditionDict + + let _routeID = _RouteID(event: Optional>.None, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRoute(_routeID) + } + } + + // MARK: addRoute (no-event) + conditional handler + + public func addRoute(transition: Transition, condition: Condition? = nil, handler: Handler) -> Disposable + { + let route = Route(transition: transition, condition: condition) + return self.addRoute(route, handler: handler) + } + + public func addRoute(route: Route, handler: Handler) -> Disposable + { + let transition = route.transition + let condition = route.condition + + let routeDisposable = self.addRoute(transition, condition: condition) + + let handlerDisposable = self.addHandler(transition) { context in + if _canPassCondition(condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + handler(context) + } + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRoute + + private func _removeRoute(_routeID: _RouteID) -> Bool + { + guard _routeID.event == nil else { + return false + } + + let transition = _routeID.transition + + guard let keyConditionDict_ = self._routes[transition] else { + return false + } + var keyConditionDict = keyConditionDict_ + + let removed = keyConditionDict.removeValueForKey(_routeID.key) != nil + + if keyConditionDict.count > 0 { + self._routes[transition] = keyConditionDict + } + else { + self._routes[transition] = nil + } + + return removed + } + + //-------------------------------------------------- + // MARK: - Handler + //-------------------------------------------------- + + // MARK: addHandler (no-event) + + public func addHandler(transition: Transition, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + if self._handlers[transition] == nil { + self._handlers[transition] = [] + } + + let key = _createUniqueString() + + var handlerInfos = self._handlers[transition]! + let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) + _insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) + + self._handlers[transition] = handlerInfos + + let handlerID = _HandlerID(event: nil, transition: transition, key: key) + + return ActionDisposable.init { [weak self] in + self?._removeHandler(handlerID) + } + } + + // MARK: removeHandler + + private func _removeHandler(handlerID: _HandlerID) -> Bool + { + if let transition = handlerID.transition { + if let handlerInfos_ = self._handlers[transition] { + var handlerInfos = handlerInfos_ + + if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { + self._handlers[transition] = handlerInfos + return true + } + } + } + + return false + } + + //-------------------------------------------------- + // MARK: - RouteChain + //-------------------------------------------------- + // + // NOTE: + // `RouteChain` allows to register `handler` which will be invoked + // only when state-based-transitions in `RouteChain` succeeded continuously. + // + + // MARK: addRouteChain + conditional handler + + public func addRouteChain(chain: TransitionChain, condition: Condition? = nil, handler: Handler) -> Disposable + { + let routeChain = RouteChain(transitionChain: chain, condition: condition) + return self.addRouteChain(routeChain, handler: handler) + } + + public func addRouteChain(chain: RouteChain, handler: Handler) -> Disposable + { + let routeDisposables = chain.routes.map { self.addRoute($0) } + let handlerDisposable = self.addChainHandler(chain, handler: handler) + + return ActionDisposable.init { + routeDisposables.forEach { $0.dispose() } + handlerDisposable.dispose() + } + } + + // MARK: addChainHandler + + public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self.addChainHandler(RouteChain(transitionChain: chain), order: order, handler: handler) + } + + public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self._addChainHandler(chain, order: order, handler: handler, isError: false) + } + + // MARK: addChainErrorHandler + + public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self.addChainErrorHandler(RouteChain(transitionChain: chain), order: order, handler: handler) + } + + public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> Disposable + { + return self._addChainHandler(chain, order: order, handler: handler, isError: true) + } + + private func _addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler, isError: Bool) -> Disposable + { + var handlerDisposables: [Disposable] = [] + + var shouldStop = true + var shouldIncrementChainingCount = true + var chainingCount = 0 + var allCount = 0 + + // reset count on 1st route + let firstRoute = chain.routes.first! + var handlerDisposable = self.addHandler(firstRoute.transition) { context in + if _canPassCondition(firstRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if shouldStop { + shouldStop = false + chainingCount = 0 + allCount = 0 + } + } + } + handlerDisposables += [handlerDisposable] + + // increment chainingCount on every route + for route in chain.routes { + + handlerDisposable = self.addHandler(route.transition) { context in + // skip duplicated transition handlers e.g. chain = 0 => 1 => 0 => 1 & transiting 0 => 1 + if !shouldIncrementChainingCount { return } + + if _canPassCondition(route.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if !shouldStop { + chainingCount++ + + shouldIncrementChainingCount = false + } + } + } + handlerDisposables += [handlerDisposable] + } + + // increment allCount (+ invoke chainErrorHandler) on any routes + handlerDisposable = self.addHandler(.Any => .Any, order: 150) { context in + + shouldIncrementChainingCount = true + + if !shouldStop { + allCount++ + } + + if chainingCount < allCount { + shouldStop = true + if isError { + handler(context) + } + } + } + handlerDisposables += [handlerDisposable] + + // invoke chainHandler on last route + let lastRoute = chain.routes.last! + handlerDisposable = self.addHandler(lastRoute.transition, order: 200) { context in + if _canPassCondition(lastRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { + if chainingCount == allCount && chainingCount == chain.routes.count && chainingCount == chain.routes.count { + shouldStop = true + + if !isError { + handler(context) + } + } + } + } + handlerDisposables += [handlerDisposable] + + return ActionDisposable.init { + handlerDisposables.forEach { $0.dispose() } + } + } + + //-------------------------------------------------- + // MARK: - StateRouteMapping + //-------------------------------------------------- + + // MARK: addRouteMapping + + public func addRouteMapping(routeMapping: StateRouteMapping) -> Disposable + { + let key = _createUniqueString() + + self._routeMappings[key] = routeMapping + + let routeMappingID = _RouteMappingID(key: key) + + return ActionDisposable.init { [weak self] in + self?._removeRouteMapping(routeMappingID) + } + } + + // MARK: addRouteMapping + conditional handler + + public func addRouteMapping(routeMapping: StateRouteMapping, handler: Handler) -> Disposable + { + let routeDisposable = self.addRouteMapping(routeMapping) + + let handlerDisposable = self.addHandler(.Any => .Any) { context in + if self._hasRouteMappingInDict(fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) != nil { + handler(context) + } + } + + return ActionDisposable.init { + routeDisposable.dispose() + handlerDisposable.dispose() + } + } + + // MARK: removeRouteMapping + + private func _removeRouteMapping(routeMappingID: _RouteMappingID) -> Bool + { + if self._routeMappings[routeMappingID.key] != nil { + self._routeMappings[routeMappingID.key] = nil + return true + } + else { + return false + } + } + +} + +//-------------------------------------------------- +// MARK: - Custom Operators +//-------------------------------------------------- + +// MARK: `<-` (tryState) + +infix operator <- { associativity left } + +public func <- (machine: StateMachine, state: S) -> StateMachine +{ + machine.tryState(state) + return machine +} + +public func <- (machine: StateMachine, tuple: (S, Any?)) -> StateMachine +{ + machine.tryState(tuple.0, userInfo: tuple.1) + return machine +} \ No newline at end of file diff --git a/SwiftState/StateType.swift b/Sources/StateType.swift similarity index 88% rename from SwiftState/StateType.swift rename to Sources/StateType.swift index 591f3e2..bb538ac 100644 --- a/SwiftState/StateType.swift +++ b/Sources/StateType.swift @@ -20,7 +20,7 @@ public enum State: Hashable { switch self { case .Some(let x): return x.hashValue - case .Any: return -4611686018427387904 + case .Any: return _hashValueForAny } } @@ -46,4 +46,8 @@ public func == (lhs: State, rhs: S) -> Bool public func == (lhs: S, rhs: State) -> Bool { return lhs.hashValue == rhs.hashValue -} \ No newline at end of file +} + +// MARK: Private + +internal let _hashValueForAny = Int.min/2 diff --git a/SwiftState/SwiftState.h b/Sources/SwiftState.h similarity index 100% rename from SwiftState/SwiftState.h rename to Sources/SwiftState.h diff --git a/SwiftState/Transition.swift b/Sources/Transition.swift similarity index 94% rename from SwiftState/Transition.swift rename to Sources/Transition.swift index 3116e9a..e37aabc 100644 --- a/SwiftState/Transition.swift +++ b/Sources/Transition.swift @@ -6,6 +6,10 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // +/// +/// "From-" and "to-" states represented as `.State1 => .State2`. +/// Also, `.Any` can be used to represent _any state_. +/// public struct Transition: Hashable { public let fromState: State diff --git a/SwiftState/TransitionChain.swift b/Sources/TransitionChain.swift similarity index 96% rename from SwiftState/TransitionChain.swift rename to Sources/TransitionChain.swift index 3b8f8f0..29306f0 100644 --- a/SwiftState/TransitionChain.swift +++ b/Sources/TransitionChain.swift @@ -6,6 +6,7 @@ // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. // +/// Group of continuous `Transition`s represented as `.State1 => .State2 => .State3`. public struct TransitionChain { public private(set) var states: [State] diff --git a/SwiftState/HandlerID.swift b/Sources/_HandlerID.swift similarity index 60% rename from SwiftState/HandlerID.swift rename to Sources/_HandlerID.swift index 5b02858..8f8daf6 100644 --- a/SwiftState/HandlerID.swift +++ b/Sources/_HandlerID.swift @@ -1,20 +1,23 @@ // -// HandlerID.swift +// _HandlerID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class HandlerID +internal final class _HandlerID { + internal let event: Event? + /// - Note: `nil` is used for error-handlerID internal let transition: Transition? internal let key: String - internal init(transition: Transition?, key: String) + internal init(event: Event?, transition: Transition?, key: String) { + self.event = event self.transition = transition self.key = key } diff --git a/Sources/_HandlerInfo.swift b/Sources/_HandlerInfo.swift new file mode 100644 index 0000000..a8b8ae1 --- /dev/null +++ b/Sources/_HandlerInfo.swift @@ -0,0 +1,21 @@ +// +// _HandlerInfo.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +internal final class _HandlerInfo +{ + internal let order: HandlerOrder + internal let key: String + internal let handler: Machine.Handler + + internal init(order: HandlerOrder, key: String, handler: Machine.Handler) + { + self.order = order + self.key = key + self.handler = handler + } +} \ No newline at end of file diff --git a/Sources/_Random.swift b/Sources/_Random.swift new file mode 100644 index 0000000..43c01e4 --- /dev/null +++ b/Sources/_Random.swift @@ -0,0 +1,22 @@ +// +// _Random.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. +// + +#if os(OSX) || os(iOS) +import Darwin +#else +import Glibc +#endif + +internal func _random(upperBound: Int) -> Int +{ + #if os(OSX) || os(iOS) + return Int(arc4random_uniform(UInt32(upperBound))) + #else + return Int(random() % upperBound) + #endif +} diff --git a/SwiftState/RouteID.swift b/Sources/_RouteID.swift similarity index 64% rename from SwiftState/RouteID.swift rename to Sources/_RouteID.swift index e52d350..e3de663 100644 --- a/SwiftState/RouteID.swift +++ b/Sources/_RouteID.swift @@ -1,21 +1,21 @@ // -// RouteID.swift +// _RouteID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class RouteID +internal final class _RouteID { - internal let event: _Event + internal let event: Event? internal let transition: Transition internal let key: String - internal init(event: _Event, transition: Transition, key: String) + internal init(event: Event?, transition: Transition, key: String) { - self.transition = transition self.event = event + self.transition = transition self.key = key } } \ No newline at end of file diff --git a/SwiftState/RouteMappingID.swift b/Sources/_RouteMappingID.swift similarity index 78% rename from SwiftState/RouteMappingID.swift rename to Sources/_RouteMappingID.swift index f36a11f..7fae7ce 100644 --- a/SwiftState/RouteMappingID.swift +++ b/Sources/_RouteMappingID.swift @@ -1,12 +1,12 @@ // -// RouteMappingID.swift +// _RouteMappingID.swift // SwiftState // // Created by Yasuhiro Inami on 2015-11-10. // Copyright © 2015 Yasuhiro Inami. All rights reserved. // -public final class RouteMappingID +internal final class _RouteMappingID { internal let key: String diff --git a/SwiftState.xcodeproj/project.pbxproj b/SwiftState.xcodeproj/project.pbxproj index d3087e6..0d85c15 100644 --- a/SwiftState.xcodeproj/project.pbxproj +++ b/SwiftState.xcodeproj/project.pbxproj @@ -8,26 +8,29 @@ /* Begin PBXBuildFile section */ 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; - 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; - 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */; }; 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */; }; 1F24C72C19D068B900C2FDC7 /* SwiftState.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4872D5AC19B4211900F326B5 /* SwiftState.framework */; }; 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */; }; - 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; - 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* RouteID.swift */; }; - 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; - 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */; }; - 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; - 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */; }; - 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; - 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */; }; + 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; + 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C531C12B5BC00D3813A /* Disposable.swift */; }; + 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; + 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */; }; + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; + 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C601C12D7BD00D3813A /* MiscTests.swift */; }; + 1F532C641C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; + 1F532C651C12EA8000D3813A /* _Random.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F532C631C12EA8000D3813A /* _Random.swift */; }; + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; + 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */; }; + 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; + 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */; }; + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; + 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */; }; 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */; }; 1FA620061996601000460108 /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; 1FA620201996606300460108 /* EventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA620191996606200460108 /* EventType.swift */; }; - 1FA620211996606300460108 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.swift */; }; 1FA620221996606300460108 /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; 1FA620231996606300460108 /* RouteChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201C1996606300460108 /* RouteChain.swift */; }; 1FA620241996606300460108 /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; @@ -37,8 +40,8 @@ 1FA62031199660CA00460108 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62028199660CA00460108 /* BasicTests.swift */; }; 1FA62032199660CA00460108 /* MyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62029199660CA00460108 /* MyState.swift */; }; 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; - 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; - 1FA62035199660CA00460108 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.swift */; }; + 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; + 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.swift */; }; 1FA62036199660CA00460108 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; @@ -49,14 +52,19 @@ 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FB1EC89199E515B00ABD937 /* MyEvent.swift */; }; 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA62028199660CA00460108 /* BasicTests.swift */; }; 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1F198C5B19972320001C3700 /* QiitaTests.swift */; }; - 4822F0AD19D008EB00F5F572 /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* MachineTests.swift */; }; + 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202C199660CA00460108 /* StateMachineTests.swift */; }; 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202A199660CA00460108 /* RouteChainTests.swift */; }; - 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* TryEventTests.swift */; }; + 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */; }; 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202F199660CA00460108 /* TransitionTests.swift */; }; 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202E199660CA00460108 /* TransitionChainTests.swift */; }; 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6202D199660CA00460108 /* RouteTests.swift */; }; + 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; + 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4836FF591C0EFD700038B7D2 /* StateMachine.swift */; }; + 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; + 483F35571C0EB192007C70D7 /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 483F35561C0EB192007C70D7 /* Machine.swift */; }; + 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; + 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4876510E1C0ECBEB005961AC /* MachineTests.swift */; }; 48797D5D19B42CBE0085D80F /* SwiftState.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FA620051996601000460108 /* SwiftState.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201A1996606300460108 /* Machine.swift */; }; 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201D1996606300460108 /* Transition.swift */; }; 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201E1996606300460108 /* TransitionChain.swift */; }; 48797D6119B42CCE0085D80F /* Route.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1FA6201B1996606300460108 /* Route.swift */; }; @@ -77,21 +85,21 @@ /* Begin PBXFileReference section */ 1F198C5B19972320001C3700 /* QiitaTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QiitaTests.swift; sourceTree = ""; }; - 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChainHandlerID.swift; sourceTree = ""; }; 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HierarchicalMachineTests.swift; sourceTree = ""; }; 1F27771C1BE68C7F00C57CC9 /* RouteMappingTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingTests.swift; sourceTree = ""; }; - 1F70FB651BF0F46000E5AC8C /* RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteID.swift; sourceTree = ""; }; - 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainID.swift; sourceTree = ""; }; - 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteMappingID.swift; sourceTree = ""; }; - 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HandlerID.swift; sourceTree = ""; }; + 1F532C531C12B5BC00D3813A /* Disposable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Disposable.swift; sourceTree = ""; }; + 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _HandlerInfo.swift; sourceTree = ""; }; + 1F532C601C12D7BD00D3813A /* MiscTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MiscTests.swift; sourceTree = ""; }; + 1F532C631C12EA8000D3813A /* _Random.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _Random.swift; sourceTree = ""; }; + 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RouteID.swift; sourceTree = ""; }; + 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _RouteMappingID.swift; sourceTree = ""; }; + 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = _HandlerID.swift; sourceTree = ""; }; 1F70FB761BF0FB2400E5AC8C /* String+TestExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+TestExt.swift"; sourceTree = ""; }; 1FA620001996601000460108 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 1FA620041996601000460108 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1FA620051996601000460108 /* SwiftState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SwiftState.h; sourceTree = ""; }; 1FA6200B1996601000460108 /* SwiftState-OSXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftState-OSXTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 1FA6200E1996601000460108 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 1FA620191996606200460108 /* EventType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventType.swift; sourceTree = ""; }; - 1FA6201A1996606300460108 /* Machine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Machine.swift; sourceTree = ""; }; 1FA6201B1996606300460108 /* Route.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Route.swift; sourceTree = ""; }; 1FA6201C1996606300460108 /* RouteChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChain.swift; sourceTree = ""; }; 1FA6201D1996606300460108 /* Transition.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Transition.swift; sourceTree = ""; }; @@ -101,14 +109,17 @@ 1FA62028199660CA00460108 /* BasicTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasicTests.swift; sourceTree = ""; }; 1FA62029199660CA00460108 /* MyState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyState.swift; sourceTree = ""; }; 1FA6202A199660CA00460108 /* RouteChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteChainTests.swift; sourceTree = ""; }; - 1FA6202B199660CA00460108 /* TryEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TryEventTests.swift; sourceTree = ""; }; - 1FA6202C199660CA00460108 /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.swift; sourceTree = ""; }; + 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachineEventTests.swift; sourceTree = ""; }; + 1FA6202C199660CA00460108 /* StateMachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachineTests.swift; sourceTree = ""; }; 1FA6202D199660CA00460108 /* RouteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RouteTests.swift; sourceTree = ""; }; 1FA6202E199660CA00460108 /* TransitionChainTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionChainTests.swift; sourceTree = ""; }; 1FA6202F199660CA00460108 /* TransitionTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransitionTests.swift; sourceTree = ""; }; 1FB1EC89199E515B00ABD937 /* MyEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MyEvent.swift; sourceTree = ""; }; 4822F0A619D0085E00F5F572 /* SwiftStateTests-iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "SwiftStateTests-iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; + 4836FF591C0EFD700038B7D2 /* StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateMachine.swift; sourceTree = ""; }; + 483F35561C0EB192007C70D7 /* Machine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Machine.swift; sourceTree = ""; }; 4872D5AC19B4211900F326B5 /* SwiftState.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SwiftState.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 4876510E1C0ECBEB005961AC /* MachineTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MachineTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -145,16 +156,16 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 1F70FB741BF0FAB900E5AC8C /* Identifiers */ = { + 1F70FB741BF0FAB900E5AC8C /* Internals */ = { isa = PBXGroup; children = ( - 1F70FB651BF0F46000E5AC8C /* RouteID.swift */, - 1F70FB681BF0F46E00E5AC8C /* RouteChainID.swift */, - 1F70FB6B1BF0F47700E5AC8C /* RouteMappingID.swift */, - 1F70FB6E1BF0F59600E5AC8C /* HandlerID.swift */, - 1F1F74BB1C09FAA000675EAA /* ChainHandlerID.swift */, + 1F70FB651BF0F46000E5AC8C /* _RouteID.swift */, + 1F70FB6B1BF0F47700E5AC8C /* _RouteMappingID.swift */, + 1F70FB6E1BF0F59600E5AC8C /* _HandlerID.swift */, + 1F532C561C12BBBB00D3813A /* _HandlerInfo.swift */, + 1F532C631C12EA8000D3813A /* _Random.swift */, ); - name = Identifiers; + name = Internals; sourceTree = ""; }; 1F70FB751BF0FAE800E5AC8C /* Routes */ = { @@ -171,8 +182,8 @@ 1FA61FF61996601000460108 = { isa = PBXGroup; children = ( - 1FA620021996601000460108 /* SwiftState */, - 1FA6200C1996601000460108 /* SwiftStateTests */, + 1FA620021996601000460108 /* Sources */, + 1FA6200C1996601000460108 /* Tests */, 1FA620011996601000460108 /* Products */, ); sourceTree = ""; @@ -188,36 +199,31 @@ name = Products; sourceTree = ""; }; - 1FA620021996601000460108 /* SwiftState */ = { + 1FA620021996601000460108 /* Sources */ = { isa = PBXGroup; children = ( 1FA620051996601000460108 /* SwiftState.h */, - 1FA6201A1996606300460108 /* Machine.swift */, + 483F35561C0EB192007C70D7 /* Machine.swift */, + 4836FF591C0EFD700038B7D2 /* StateMachine.swift */, + 1F532C531C12B5BC00D3813A /* Disposable.swift */, 1FB1EC88199E4ABC00ABD937 /* Protocols */, 1F70FB751BF0FAE800E5AC8C /* Routes */, - 1F70FB741BF0FAB900E5AC8C /* Identifiers */, - 1FA620031996601000460108 /* Supporting Files */, + 1F70FB741BF0FAB900E5AC8C /* Internals */, ); - path = SwiftState; + path = Sources; sourceTree = ""; }; - 1FA620031996601000460108 /* Supporting Files */ = { - isa = PBXGroup; - children = ( - 1FA620041996601000460108 /* Info.plist */, - ); - name = "Supporting Files"; - sourceTree = ""; - }; - 1FA6200C1996601000460108 /* SwiftStateTests */ = { + 1FA6200C1996601000460108 /* Tests */ = { isa = PBXGroup; children = ( 1FA62027199660CA00460108 /* _TestCase.swift */, 1FB1EC8D199E609900ABD937 /* State & Event */, 1FA62028199660CA00460108 /* BasicTests.swift */, + 1F532C601C12D7BD00D3813A /* MiscTests.swift */, 1F198C5B19972320001C3700 /* QiitaTests.swift */, - 1FA6202C199660CA00460108 /* MachineTests.swift */, - 1FA6202B199660CA00460108 /* TryEventTests.swift */, + 4876510E1C0ECBEB005961AC /* MachineTests.swift */, + 1FA6202C199660CA00460108 /* StateMachineTests.swift */, + 1FA6202B199660CA00460108 /* StateMachineEventTests.swift */, 1FA6202F199660CA00460108 /* TransitionTests.swift */, 1FA6202E199660CA00460108 /* TransitionChainTests.swift */, 1FA6202D199660CA00460108 /* RouteTests.swift */, @@ -226,7 +232,7 @@ 1F1F74C11C0A02E700675EAA /* HierarchicalMachineTests.swift */, 1FA6200D1996601000460108 /* Supporting Files */, ); - path = SwiftStateTests; + path = Tests; sourceTree = ""; }; 1FA6200D1996601000460108 /* Supporting Files */ = { @@ -426,16 +432,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F70FB691BF0F46E00E5AC8C /* RouteChainID.swift in Sources */, + 1F532C641C12EA8000D3813A /* _Random.swift in Sources */, 1FA620251996606300460108 /* TransitionChain.swift in Sources */, + 1F532C541C12B5BC00D3813A /* Disposable.swift in Sources */, 1FA620241996606300460108 /* Transition.swift in Sources */, - 1F70FB661BF0F46000E5AC8C /* RouteID.swift in Sources */, + 1F70FB661BF0F46000E5AC8C /* _RouteID.swift in Sources */, 1FA620201996606300460108 /* EventType.swift in Sources */, - 1F1F74BC1C09FAA000675EAA /* ChainHandlerID.swift in Sources */, - 1F70FB6C1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, + 1F532C571C12BBBB00D3813A /* _HandlerInfo.swift in Sources */, + 4836FF5A1C0EFD700038B7D2 /* StateMachine.swift in Sources */, + 483F35571C0EB192007C70D7 /* Machine.swift in Sources */, + 1F70FB6C1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */, 1FA620221996606300460108 /* Route.swift in Sources */, - 1F70FB6F1BF0F59600E5AC8C /* HandlerID.swift in Sources */, - 1FA620211996606300460108 /* Machine.swift in Sources */, + 1F70FB6F1BF0F59600E5AC8C /* _HandlerID.swift in Sources */, 1FA620261996606300460108 /* StateType.swift in Sources */, 1FA620231996606300460108 /* RouteChain.swift in Sources */, ); @@ -448,14 +456,16 @@ 1FB1EC8A199E515B00ABD937 /* MyEvent.swift in Sources */, 1F198C5C19972320001C3700 /* QiitaTests.swift in Sources */, 1F70FB791BF0FB7000E5AC8C /* String+TestExt.swift in Sources */, + 4876510F1C0ECBEB005961AC /* MachineTests.swift in Sources */, 1FA62038199660CA00460108 /* TransitionTests.swift in Sources */, 1FA62033199660CA00460108 /* RouteChainTests.swift in Sources */, 1FA62030199660CA00460108 /* _TestCase.swift in Sources */, 1F1F74C31C0A02EA00675EAA /* HierarchicalMachineTests.swift in Sources */, 1FA62036199660CA00460108 /* RouteTests.swift in Sources */, + 1F532C611C12D7BD00D3813A /* MiscTests.swift in Sources */, 1FA62031199660CA00460108 /* BasicTests.swift in Sources */, - 1FA62034199660CA00460108 /* TryEventTests.swift in Sources */, - 1FA62035199660CA00460108 /* MachineTests.swift in Sources */, + 1FA62034199660CA00460108 /* StateMachineEventTests.swift in Sources */, + 1FA62035199660CA00460108 /* StateMachineTests.swift in Sources */, 1FA62037199660CA00460108 /* TransitionChainTests.swift in Sources */, 1F27771E1BE68D1D00C57CC9 /* RouteMappingTests.swift in Sources */, 1FA62032199660CA00460108 /* MyState.swift in Sources */, @@ -469,17 +479,19 @@ 4822F0AC19D008EB00F5F572 /* QiitaTests.swift in Sources */, 4822F0AB19D008EB00F5F572 /* BasicTests.swift in Sources */, 1F70FB7A1BF0FB7100E5AC8C /* String+TestExt.swift in Sources */, - 4822F0AF19D008EB00F5F572 /* TryEventTests.swift in Sources */, + 487651101C0ECBEB005961AC /* MachineTests.swift in Sources */, + 4822F0AF19D008EB00F5F572 /* StateMachineEventTests.swift in Sources */, 4822F0A819D008E300F5F572 /* _TestCase.swift in Sources */, 4822F0AA19D008E700F5F572 /* MyEvent.swift in Sources */, 1F1F74C41C0A02EB00675EAA /* HierarchicalMachineTests.swift in Sources */, 4822F0AE19D008EB00F5F572 /* RouteChainTests.swift in Sources */, + 1F532C621C12D7BD00D3813A /* MiscTests.swift in Sources */, 4822F0B019D008EB00F5F572 /* TransitionTests.swift in Sources */, 4822F0A919D008E700F5F572 /* MyState.swift in Sources */, 4822F0B219D008EB00F5F572 /* RouteTests.swift in Sources */, 4822F0B119D008EB00F5F572 /* TransitionChainTests.swift in Sources */, 1F27771F1BE68D1E00C57CC9 /* RouteMappingTests.swift in Sources */, - 4822F0AD19D008EB00F5F572 /* MachineTests.swift in Sources */, + 4822F0AD19D008EB00F5F572 /* StateMachineTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -487,16 +499,18 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 1F70FB6A1BF0F46E00E5AC8C /* RouteChainID.swift in Sources */, + 1F532C651C12EA8000D3813A /* _Random.swift in Sources */, 48797D6219B42CCE0085D80F /* RouteChain.swift in Sources */, + 1F532C551C12B5BC00D3813A /* Disposable.swift in Sources */, 48797D6019B42CCE0085D80F /* TransitionChain.swift in Sources */, - 1F70FB671BF0F46000E5AC8C /* RouteID.swift in Sources */, + 1F70FB671BF0F46000E5AC8C /* _RouteID.swift in Sources */, + 4836FF5C1C0EFD720038B7D2 /* Machine.swift in Sources */, + 1F532C581C12BBBB00D3813A /* _HandlerInfo.swift in Sources */, 48797D5F19B42CCE0085D80F /* Transition.swift in Sources */, - 1F1F74C01C0A017E00675EAA /* ChainHandlerID.swift in Sources */, - 1F70FB6D1BF0F47700E5AC8C /* RouteMappingID.swift in Sources */, + 1F70FB6D1BF0F47700E5AC8C /* _RouteMappingID.swift in Sources */, 48797D6319B42CD40085D80F /* StateType.swift in Sources */, - 1F70FB701BF0F59600E5AC8C /* HandlerID.swift in Sources */, - 48797D5E19B42CCE0085D80F /* Machine.swift in Sources */, + 4836FF5B1C0EFD700038B7D2 /* StateMachine.swift in Sources */, + 1F70FB701BF0F59600E5AC8C /* _HandlerID.swift in Sources */, 48797D6119B42CCE0085D80F /* Route.swift in Sources */, 48797D6419B42CD40085D80F /* EventType.swift in Sources */, ); @@ -609,7 +623,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SwiftState/Info.plist; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -631,7 +645,7 @@ DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; FRAMEWORK_VERSION = A; - INFOPLIST_FILE = SwiftState/Info.plist; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; MACOSX_DEPLOYMENT_TARGET = 10.9; @@ -639,6 +653,7 @@ PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -654,7 +669,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -670,7 +685,7 @@ "$(DEVELOPER_FRAMEWORKS_DIR)", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -689,7 +704,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; @@ -705,7 +720,7 @@ "$(SDKROOT)/Developer/Library/Frameworks", "$(inherited)", ); - INFOPLIST_FILE = SwiftStateTests/Info.plist; + INFOPLIST_FILE = Tests/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; @@ -728,7 +743,7 @@ "DEBUG=1", "$(inherited)", ); - INFOPLIST_FILE = "$(SRCROOT)/SwiftState/Info.plist"; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; @@ -748,13 +763,14 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - INFOPLIST_FILE = "$(SRCROOT)/SwiftState/Info.plist"; + INFOPLIST_FILE = Sources/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.inamiy.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(PROJECT_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; diff --git a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme index 1231e10..c5db47c 100644 --- a/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme +++ b/SwiftState.xcodeproj/xcshareddata/xcschemes/SwiftState-OSX.xcscheme @@ -23,10 +23,10 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -52,11 +52,11 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -52,11 +52,11 @@ -{ - internal let bundledHandlerIDs: [HandlerID] - - internal init(bundledHandlerIDs: [HandlerID]) - { - self.bundledHandlerIDs = bundledHandlerIDs - } -} \ No newline at end of file diff --git a/SwiftState/EventType.swift b/SwiftState/EventType.swift deleted file mode 100644 index 613487c..0000000 --- a/SwiftState/EventType.swift +++ /dev/null @@ -1,104 +0,0 @@ -// -// EventType.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/05. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -public protocol EventType: Hashable {} - -// MARK: Event (public) - -/// `EventType` wrapper for handling`.Any` event. -public enum Event: Equatable -{ - case Some(E) - case Any - - public var value: E? - { - switch self { - case .Some(let x): return x - default: return nil - } - } - - internal func _toInternal() -> _Event - { - switch self { - case .Some(let x): return .Some(x) - case .Any: return .Any - } - } -} - -public func == (lhs: Event, rhs: Event) -> Bool -{ - switch (lhs, rhs) { - case let (.Some(x1), .Some(x2)) where x1 == x2: - return true - case (.Any, .Any): - return true - default: - return false - } -} - -// MARK: NoEvent - -/// Useful for creating StateMachine without events, i.e. `Machine`. -public enum NoEvent: EventType -{ - public var hashValue: Int - { - return 0 - } -} - -public func == (lhs: NoEvent, rhs: NoEvent) -> Bool -{ - return true -} - -// MARK: _Event (internal) - -/// Internal `EventType` wrapper for `Event` + `Optional` + `Hashable`. -internal enum _Event: Hashable -{ - case Some(E) - case Any // represents any `Some(E)` events but not `.None`, for `addRouteEvent(.Any)` - case None // default internal value for `addRoute()` without event - - internal var hashValue: Int - { - switch self { - case .Some(let x): return x.hashValue - case .Any: return -4611686018427387904 - case .None: return -4611686018427387905 - } - } - - internal var value: E? - { - switch self { - case .Some(let x): return x - default: return nil - } - } -} - -internal func == (lhs: _Event, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: _Event, rhs: E) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} - -internal func == (lhs: E, rhs: _Event) -> Bool -{ - return lhs.hashValue == rhs.hashValue -} diff --git a/SwiftState/Machine.swift b/SwiftState/Machine.swift deleted file mode 100644 index 99929b5..0000000 --- a/SwiftState/Machine.swift +++ /dev/null @@ -1,851 +0,0 @@ -// -// Machine.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2014/08/03. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. -// - -import Darwin - -public typealias HandlerOrder = UInt8 -private let _defaultOrder: HandlerOrder = 100 - -public final class Machine -{ - // NOTE: `event = nil` is equivalent to `_Event.None`, which happens when non-event-based transition e.g. `tryState()` occurs. - public typealias Context = (event: E?, fromState: S, toState: S, userInfo: Any?) - - public typealias Condition = (Context -> Bool) - - public typealias Handler = Context -> () - - public typealias RouteMapping = (event: E?, fromState: S, userInfo: Any?) -> S? - - private var _routes: [_Event : [Transition : [String : Condition?]]] = [:] - - private var _routeMappings: [String : RouteMapping] = [:] - - private var _handlers: [Transition : [_HandlerInfo]] = [:] - private var _errorHandlers: [_HandlerInfo] = [] - - internal var _state: S - - //-------------------------------------------------- - // MARK: - Init - //-------------------------------------------------- - - public init(state: S, initClosure: (Machine -> ())? = nil) - { - self._state = state - - initClosure?(self) - } - - public func configure(closure: Machine -> ()) - { - closure(self) - } - - //-------------------------------------------------- - // MARK: - State/Event/Transition - //-------------------------------------------------- - - public var state: S - { - return self._state - } - - public func hasRoute(transition: Transition, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - guard let fromState = transition.fromState.value, - toState = transition.toState.value else - { - assertionFailure("State = `.Any` is not supported for `hasRoute()` (always returns `false`)") - return false - } - - return self.hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) - } - - public func hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - if _hasRoute(fromState: fromState, toState: toState, forEvent: event, userInfo: userInfo) { - return true - } - - if _hasRouteMapping(fromState: fromState, toState: .Some(toState), forEvent: event, userInfo: userInfo) != nil { - return true - } - - return false - } - - /// - /// Check for `_routes`. - /// - /// - Parameter event: - /// If `event` is nil, all registered routes will be examined. - /// Otherwise, only routes for `event` and `.Any` will be examined. - /// - private func _hasRoute(fromState fromState: S, toState: S, forEvent event: E? = nil, userInfo: Any? = nil) -> Bool - { - let validTransitions = _validTransitions(fromState: fromState, toState: toState) - - for validTransition in validTransitions { - - var transitionDicts: [[Transition : [String : Condition?]]] = [] - - if let event = event { - for (_event, transitionDict) in self._routes { - // NOTE: `_event = .None` should be excluded - if _event.value == event || _event == .Any { - transitionDicts += [transitionDict] - } - } - } - else { - transitionDicts += self._routes.values.lazy - } - - // check for `_routes - for transitionDict in transitionDicts { - if let keyConditionDict = transitionDict[validTransition] { - for (_, condition) in keyConditionDict { - if _canPassCondition(condition, forEvent: event, fromState: fromState, toState: toState, userInfo: userInfo) { - return true - } - } - } - } - } - - return false - } - - /// - /// Check for `_routeMappings`. - /// - /// - Returns: Preferred `mapped-toState` in case of `toState = .Any`. - /// - private func _hasRouteMapping(fromState fromState: S, toState: State, forEvent event: E? = nil, userInfo: Any? = nil) -> S? - { - for mapping in self._routeMappings.values { - if let mappedToState = mapping(event: event, fromState: fromState, userInfo: userInfo) - where mappedToState == toState.value || toState == .Any - { - return mappedToState - } - } - - return nil - } - - public func canTryState(toState: S, forEvent event: E? = nil) -> Bool - { - let fromState = self.state - - return self.hasRoute(fromState: fromState, toState: toState, forEvent: event) - } - - public func canTryState(toState: S, forEvent event: E) -> Bool - { - return self.canTryState(toState, forEvent: .Some(event)) - } - - public func tryState(toState: S, userInfo: Any? = nil) -> Bool - { - return self._tryState(toState, userInfo: userInfo, forEvent: nil) - } - - internal func _tryState(toState: S, userInfo: Any? = nil, forEvent event: E?) -> Bool - { - var didTransit = false - - let fromState = self.state - - if self.canTryState(toState, forEvent: event) { - - // collect valid handlers before updating state - let validHandlerInfos = self._validHandlerInfosForTransition(fromState: fromState, toState: toState) - - // update state - self._state = toState - - // - // Perform validHandlers after updating state. - // - // NOTE: - // Instead of using before/after handlers as seen in many other Machine libraries, - // SwiftState uses `order` value to perform handlers in 'fine-grained' order, - // only after state has been updated. (Any problem?) - // - for handlerInfo in validHandlerInfos { - handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) - } - - didTransit = true - } - else { - for handlerInfo in self._errorHandlers { - handlerInfo.handler(Context(event: event, fromState: fromState, toState: toState, userInfo: userInfo)) - } - } - - return didTransit - } - - private func _validHandlerInfosForTransition(fromState fromState: S, toState: S) -> [_HandlerInfo] - { - var validHandlerInfos: [_HandlerInfo] = [] - - let validTransitions = _validTransitions(fromState: fromState, toState: toState) - - for validTransition in validTransitions { - if let handlerInfos = self._handlers[validTransition] { - for handlerInfo in handlerInfos { - validHandlerInfos += [handlerInfo] - } - } - } - - validHandlerInfos.sortInPlace { info1, info2 in - return info1.order < info2.order - } - - return validHandlerInfos - } - - public func canTryEvent(event: E, userInfo: Any? = nil) -> S? - { - if let transitionDict = self._routes[_Event.Some(event)] { - for (transition, keyConditionDict) in transitionDict { - if transition.fromState == .Some(self.state) || transition.fromState == .Any { - for (_, condition) in keyConditionDict { - // if toState is `.Any`, it means identity transition - let toState = transition.toState.value ?? self.state - - if _canPassCondition(condition, forEvent: .Some(event), fromState: self.state, toState: toState, userInfo: userInfo) { - return toState - } - } - } - } - } - - if let toState = _hasRouteMapping(fromState: self.state, toState: .Any, forEvent: event, userInfo: userInfo) { - return toState - } - - return nil - } - - public func tryEvent(event: E, userInfo: Any? = nil) -> Bool - { - if let toState = self.canTryEvent(event, userInfo: userInfo) { - self._tryState(toState, userInfo: userInfo, forEvent: .Some(event)) - return true - } - - return false - } - - //-------------------------------------------------- - // MARK: - Route - //-------------------------------------------------- - - // MARK: addRoute - - public func addRoute(transition: Transition, condition: Condition? = nil) -> RouteID - { - let route = Route(transition: transition, condition: condition) - return self.addRoute(route) - } - - public func addRoute(route: Route) -> RouteID - { - return self._addRoute(route) - } - - internal func _addRoute(route: Route, forEvent event: _Event = .None) -> RouteID - { - let transition = route.transition - let condition = route.condition - - if self._routes[event] == nil { - self._routes[event] = [:] - } - - var transitionDict = self._routes[event]! - if transitionDict[transition] == nil { - transitionDict[transition] = [:] - } - - let key = _createUniqueString() - - var keyConditionDict = transitionDict[transition]! - keyConditionDict[key] = condition - transitionDict[transition] = keyConditionDict - - self._routes[event] = transitionDict - - let routeID = RouteID(event: event, transition: transition, key: key) - - return routeID - } - - // MARK: addRoute + conditional handler - - public func addRoute(transition: Transition, condition: Condition? = nil, handler: Handler) -> (RouteID, HandlerID) - { - let route = Route(transition: transition, condition: condition) - return self.addRoute(route, handler: handler) - } - - public func addRoute(route: Route, handler: Handler) -> (RouteID, HandlerID) - { - let transition = route.transition - let condition = route.condition - - let routeID = self.addRoute(transition, condition: condition) - - let handlerID = self.addHandler(transition) { context in - if _canPassCondition(condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { - handler(context) - } - } - - return (routeID, handlerID) - } - - // MARK: removeRoute - - public func removeRoute(routeID: RouteID) -> Bool - { - let event = routeID.event - let transition = routeID.transition - - if var transitionDict = self._routes[event] { - if var keyConditionDict = transitionDict[transition] { - keyConditionDict[routeID.key] = nil - if keyConditionDict.count > 0 { - transitionDict[transition] = keyConditionDict - } - else { - transitionDict[transition] = nil - } - } - - if transitionDict.count > 0 { - self._routes[event] = transitionDict - } - else { - self._routes[event] = nil - } - - return true - } - - return false - } - - //-------------------------------------------------- - // MARK: - Handler - //-------------------------------------------------- - - public func addHandler(transition: Transition, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - if self._handlers[transition] == nil { - self._handlers[transition] = [] - } - - let key = _createUniqueString() - - var handlerInfos = self._handlers[transition]! - let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) - _insertHandlerIntoArray(&handlerInfos, newHandlerInfo: newHandlerInfo) - - self._handlers[transition] = handlerInfos - - let handlerID = HandlerID(transition: transition, key: key) - - return handlerID - } - - // MARK: addEntryHandler - - public func addEntryHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Any => state, handler: handler) - } - - public func addEntryHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Any => .Some(state), handler: handler) - } - - // MARK: addExitHandler - - public func addExitHandler(state: State, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(state => .Any, handler: handler) - } - - public func addExitHandler(state: S, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addHandler(.Some(state) => .Any, handler: handler) - } - - // MARK: removeHandler - - public func removeHandler(handlerID: HandlerID) -> Bool - { - if let transition = handlerID.transition { - if var handlerInfos = self._handlers[transition] { - - if _removeHandlerFromArray(&handlerInfos, removingHandlerID: handlerID) { - self._handlers[transition] = handlerInfos - return true - } - } - } - // `transition = nil` means errorHandler - else { - if _removeHandlerFromArray(&self._errorHandlers, removingHandlerID: handlerID) { - return true - } - return false - } - - return false - } - - // MARK: addErrorHandler - - public func addErrorHandler(order order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - let key = _createUniqueString() - - let newHandlerInfo = _HandlerInfo(order: order, key: key, handler: handler) - _insertHandlerIntoArray(&self._errorHandlers, newHandlerInfo: newHandlerInfo) - - let handlerID = HandlerID(transition: nil, key: key) - - return handlerID - } - - //-------------------------------------------------- - // MARK: - RouteChain - //-------------------------------------------------- - // NOTE: handler is required for addRouteChain - - // MARK: addRouteChain + conditional handler - - public func addRouteChain(chain: TransitionChain, condition: Condition? = nil, handler: Handler) -> (RouteChainID, ChainHandlerID) - { - let routeChain = RouteChain(transitionChain: chain, condition: condition) - return self.addRouteChain(routeChain, handler: handler) - } - - public func addRouteChain(chain: RouteChain, handler: Handler) -> (RouteChainID, ChainHandlerID) - { - var routeIDs: [RouteID] = [] - - for route in chain.routes { - let routeID = self.addRoute(route) - routeIDs += [routeID] - } - - let chainHandlerID = self.addChainHandler(chain, handler: handler) - - let routeChainID = RouteChainID(bundledRouteIDs: routeIDs) - - return (routeChainID, chainHandlerID) - } - - // MARK: addChainHandler - - public func addChainHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self.addChainHandler(RouteChain(transitionChain: chain), order: order, handler: handler) - } - - public func addChainHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: false) - } - - // MARK: addChainErrorHandler - - public func addChainErrorHandler(chain: TransitionChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self.addChainErrorHandler(RouteChain(transitionChain: chain), order: order, handler: handler) - } - - public func addChainErrorHandler(chain: RouteChain, order: HandlerOrder = _defaultOrder, handler: Handler) -> ChainHandlerID - { - return self._addChainHandler(chain, order: order, handler: handler, isError: true) - } - - private func _addChainHandler(chain: RouteChain, order: HandlerOrder, handler: Handler, isError: Bool) -> ChainHandlerID - { - var handlerIDs: [HandlerID] = [] - - var shouldStop = true - var shouldIncrementChainingCount = true - var chainingCount = 0 - var allCount = 0 - - // reset count on 1st route - let firstRoute = chain.routes.first! - var handlerID = self.addHandler(firstRoute.transition) { context in - if _canPassCondition(firstRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { - if shouldStop { - shouldStop = false - chainingCount = 0 - allCount = 0 - } - } - } - handlerIDs += [handlerID] - - // increment chainingCount on every route - for route in chain.routes { - - handlerID = self.addHandler(route.transition) { context in - // skip duplicated transition handlers e.g. chain = 0 => 1 => 0 => 1 & transiting 0 => 1 - if !shouldIncrementChainingCount { return } - - if _canPassCondition(route.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { - if !shouldStop { - chainingCount++ - - shouldIncrementChainingCount = false - } - } - } - handlerIDs += [handlerID] - } - - // increment allCount (+ invoke chainErrorHandler) on any routes - handlerID = self.addHandler(.Any => .Any, order: 150) { context in - - shouldIncrementChainingCount = true - - if !shouldStop { - allCount++ - } - - if chainingCount < allCount { - shouldStop = true - if isError { - handler(context) - } - } - } - handlerIDs += [handlerID] - - // invoke chainHandler on last route - let lastRoute = chain.routes.last! - handlerID = self.addHandler(lastRoute.transition, order: 200) { context in - if _canPassCondition(lastRoute.condition, forEvent: nil, fromState: context.fromState, toState: context.toState, userInfo: context.userInfo) { - if chainingCount == allCount && chainingCount == chain.routes.count && chainingCount == chain.routes.count { - shouldStop = true - - if !isError { - handler(context) - } - } - } - } - handlerIDs += [handlerID] - - let chainHandlerID = ChainHandlerID(bundledHandlerIDs: handlerIDs) - - return chainHandlerID - } - - // MARK: removeRouteChain - - public func removeRouteChain(routeChainID: RouteChainID) -> Bool - { - var success = false - for bundledRouteID in routeChainID.bundledRouteIDs! { - success = self.removeRoute(bundledRouteID) || success - } - return success - } - - // MARK: removeChainHandler - - public func removeChainHandler(chainHandlerID: ChainHandlerID) -> Bool - { - var success = false - for bundledHandlerID in chainHandlerID.bundledHandlerIDs { - success = self.removeHandler(bundledHandlerID) || success - } - return success - } - - //-------------------------------------------------- - // MARK: - RouteEvent - //-------------------------------------------------- - - public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition? = nil) -> [RouteID] - { - var routes: [Route] = [] - for transition in transitions { - let route = Route(transition: transition, condition: condition) - routes += [route] - } - - return self.addRouteEvent(event, routes: routes) - } - - public func addRouteEvent(event: E, transitions: [Transition], condition: Condition? = nil) -> [RouteID] - { - return self.addRouteEvent(.Some(event), transitions: transitions, condition: condition) - } - - public func addRouteEvent(event: Event, routes: [Route]) -> [RouteID] - { - var routeIDs: [RouteID] = [] - for route in routes { - let routeID = self._addRoute(route, forEvent: event._toInternal()) - routeIDs += [routeID] - } - - return routeIDs - } - - public func addRouteEvent(event: E, routes: [Route]) -> [RouteID] - { - return self.addRouteEvent(.Some(event), routes: routes) - } - - // MARK: addRouteEvent + conditional handler - - public func addRouteEvent(event: Event, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) - { - let routeIDs = self.addRouteEvent(event, transitions: transitions, condition: condition) - - let handlerID = self.addEventHandler(event, handler: handler) - - return (routeIDs, handlerID) - } - - public func addRouteEvent(event: E, transitions: [Transition], condition: Condition? = nil, handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(.Some(event), transitions: transitions, condition: condition, handler: handler) - } - - public func addRouteEvent(event: Event, routes: [Route], handler: Handler) -> ([RouteID], HandlerID) - { - let routeIDs = self.addRouteEvent(event, routes: routes) - - let handlerID = self.addEventHandler(event, handler: handler) - - return (routeIDs, handlerID) - } - - public func addRouteEvent(event: E, routes: [Route], handler: Handler) -> ([RouteID], HandlerID) - { - return self.addRouteEvent(.Some(event), routes: routes, handler: handler) - } - - // MARK: addEventHandler - - public func addEventHandler(event: Event, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - let handlerID = self.addHandler(.Any => .Any, order: order) { context in - // skip if not event-based transition - guard let triggeredEvent = context.event else { - return - } - - if triggeredEvent == event.value || event == .Any { - handler(context) - } - } - - return handlerID - } - - public func addEventHandler(event: E, order: HandlerOrder = _defaultOrder, handler: Handler) -> HandlerID - { - return self.addEventHandler(.Some(event), order: order, handler: handler) - } - - //-------------------------------------------------- - // MARK: - RouteMapping - //-------------------------------------------------- - - // MARK: addRouteMapping - - public func addRouteMapping(routeMapping: RouteMapping) -> RouteMappingID - { - let key = _createUniqueString() - - self._routeMappings[key] = routeMapping - - let routeID = RouteMappingID(key: key) - - return routeID - } - - // MARK: addRouteMapping + conditional handler - - public func addRouteMapping(routeMapping: RouteMapping, handler: Handler) -> (RouteMappingID, HandlerID) - { - let routeMappingID = self.addRouteMapping(routeMapping) - - let handlerID = self.addHandler(.Any => .Any) { context in - if self._hasRouteMapping(fromState: context.fromState, toState: .Some(context.toState), forEvent: context.event, userInfo: context.userInfo) != nil { - - handler(context) - } - } - - return (routeMappingID, handlerID) - } - - // MARK: removeRouteMapping - - public func removeRouteMapping(routeMappingID: RouteMappingID) -> Bool - { - if self._routeMappings[routeMappingID.key] != nil { - self._routeMappings[routeMappingID.key] = nil - return true - } - else { - return false - } - } - - //-------------------------------------------------- - // MARK: - Remove All - //-------------------------------------------------- - - public func removeAllRoutes() -> Bool - { - let removingCount = self._routes.count + self._routeMappings.count - - self._routes = [:] - self._routeMappings = [:] - - return removingCount > 0 - } - - public func removeAllHandlers() -> Bool - { - let removingCount = self._handlers.count + self._errorHandlers.count - - self._handlers = [:] - self._errorHandlers = [] - - return removingCount > 0 - } - -} - -//-------------------------------------------------- -// MARK: - Custom Operators -//-------------------------------------------------- - -// MARK: <- (tryState) - -infix operator <- { associativity right } - -public func <- (machine: Machine, state: S) -> Bool -{ - return machine.tryState(state) -} - -public func <- (machine: Machine, tuple: (S, Any?)) -> Bool -{ - return machine.tryState(tuple.0, userInfo: tuple.1) -} - -// MARK: <-! (tryEvent) - -infix operator <-! { associativity right } - -public func <-! (machine: Machine, event: E) -> Bool -{ - return machine.tryEvent(event) -} - -public func <-! (machine: Machine, tuple: (E, Any?)) -> Bool -{ - return machine.tryEvent(tuple.0, userInfo: tuple.1) -} - -//-------------------------------------------------- -// MARK: - Private -//-------------------------------------------------- - -// generate approx 126bit random string -private func _createUniqueString() -> String -{ - var uniqueString: String = "" - for _ in 1...8 { - uniqueString += String(UnicodeScalar(arc4random_uniform(0xD800))) // 0xD800 = 55296 = 15.755bit - } - return uniqueString -} - - -private func _validTransitions(fromState fromState: S, toState: S) -> [Transition] -{ - return [ - fromState => toState, - fromState => .Any, - .Any => toState, - .Any => .Any - ] -} - -private func _canPassCondition(condition: Machine.Condition?, forEvent event: E?, fromState: S, toState: S, userInfo: Any?) -> Bool -{ - return condition?((event, fromState, toState, userInfo)) ?? true -} - -private func _insertHandlerIntoArray(inout handlerInfos: [_HandlerInfo], newHandlerInfo: _HandlerInfo) -{ - var index = handlerInfos.count - - for i in Array(0..(inout handlerInfos: [_HandlerInfo], removingHandlerID: HandlerID) -> Bool -{ - for i in 0.. -{ - private let order: HandlerOrder - private let key: String - private let handler: Machine.Handler - - private init(order: HandlerOrder, key: String, handler: Machine.Handler) - { - self.order = order - self.key = key - self.handler = handler - } -} diff --git a/SwiftState/RouteChainID.swift b/SwiftState/RouteChainID.swift deleted file mode 100644 index 65abb36..0000000 --- a/SwiftState/RouteChainID.swift +++ /dev/null @@ -1,17 +0,0 @@ -// -// RouteChainID.swift -// SwiftState -// -// Created by Yasuhiro Inami on 2015-11-10. -// Copyright © 2015 Yasuhiro Inami. All rights reserved. -// - -public final class RouteChainID -{ - internal let bundledRouteIDs: [RouteID]? - - internal init(bundledRouteIDs: [RouteID]?) - { - self.bundledRouteIDs = bundledRouteIDs - } -} \ No newline at end of file diff --git a/Tests/BasicTests.swift b/Tests/BasicTests.swift new file mode 100644 index 0000000..919ae78 --- /dev/null +++ b/Tests/BasicTests.swift @@ -0,0 +1,75 @@ +// +// BasicTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/08. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class BasicTests: _TestCase +{ + func testREADME() + { + // setup state machine + let machine = StateMachine(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) + } + + func testREADME_tryEvent() + { + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") + } +} \ No newline at end of file diff --git a/SwiftStateTests/HierarchicalMachineTests.swift b/Tests/HierarchicalMachineTests.swift similarity index 95% rename from SwiftStateTests/HierarchicalMachineTests.swift rename to Tests/HierarchicalMachineTests.swift index 7d39e62..103bd7d 100644 --- a/SwiftStateTests/HierarchicalMachineTests.swift +++ b/Tests/HierarchicalMachineTests.swift @@ -64,16 +64,16 @@ class HierarchicalMachineTests: _TestCase /// - Warning: /// This is a naive implementation and easily lose consistency when `subMachine.state` is changed directly, e.g. `subMachine1 <- .SubState1`. /// - private var mainMachine: Machine<_MainState, NoEvent>? + private var mainMachine: StateMachine<_MainState, NoEvent>? - private var subMachine1: Machine<_SubState, NoEvent>? - private var subMachine2: Machine<_SubState, NoEvent>? + private var subMachine1: StateMachine<_SubState, NoEvent>? + private var subMachine2: StateMachine<_SubState, NoEvent>? override func setUp() { super.setUp() - let subMachine1 = Machine<_SubState, NoEvent>(state: .SubState0) { subMachine1 in + let subMachine1 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine1 in // add Sub1-0 => Sub1-1 subMachine1.addRoute(.SubState0 => .SubState1) @@ -81,7 +81,7 @@ class HierarchicalMachineTests: _TestCase subMachine1.addErrorHandler { print("[ERROR][Sub1] \($0.fromState) => \($0.toState)") } } - let subMachine2 = Machine<_SubState, NoEvent>(state: .SubState0) { subMachine2 in + let subMachine2 = StateMachine<_SubState, NoEvent>(state: .SubState0) { subMachine2 in // add Sub2-0 => Sub2-1 subMachine2.addRoute(.SubState0 => .SubState1) @@ -89,7 +89,7 @@ class HierarchicalMachineTests: _TestCase subMachine2.addErrorHandler { print("[ERROR][Sub2] \($0.fromState) => \($0.toState)") } } - let mainMachine = Machine<_MainState, NoEvent>(state: .MainState0) { mainMachine in + let mainMachine = StateMachine<_MainState, NoEvent>(state: .MainState0) { mainMachine in // add routes & handle for same-subMachine internal transitions mainMachine.addRoute(.Any => .Any, condition: { _, fromState, toState, userInfo in @@ -115,7 +115,7 @@ class HierarchicalMachineTests: _TestCase }) // add routes for mainMachine-state transitions (submachine switching) - mainMachine.addRouteMapping { _, fromState, _ -> _MainState? in + mainMachine.addRouteMapping { event, fromState, userInfo -> _MainState? in // NOTE: use external submachine's states only for evaluating `toState`, but not for `fromState` switch fromState { diff --git a/SwiftStateTests/Info.plist b/Tests/Info.plist similarity index 100% rename from SwiftStateTests/Info.plist rename to Tests/Info.plist diff --git a/Tests/MachineTests.swift b/Tests/MachineTests.swift new file mode 100644 index 0000000..b3b8740 --- /dev/null +++ b/Tests/MachineTests.swift @@ -0,0 +1,300 @@ +// +// MachineTests.swift +// SwiftState +// +// Created by Yasuhiro Inami on 2014/08/05. +// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// + +import SwiftState +import XCTest + +class MachineTests: _TestCase +{ + //-------------------------------------------------- + // MARK: - tryEvent a.k.a `<-!` + //-------------------------------------------------- + + func testCanTryEvent() + { + let machine = Machine(state: .State0) + + // add 0 => 1 & 1 => 2 + // (NOTE: this is not chaining e.g. 0 => 1 => 2) + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + XCTAssertTrue(machine.canTryEvent(.Event0) != nil) + } + + func testTryEvent() + { + let machine = Machine(state: .State0) { machine in + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") + } + + func testTryEvent_customOperator() + { + let machine = Machine(state: .State0) { machine in + // add 0 => 1 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + ]) + // add 0 => 1 + machine.addRoutes(event: .Event1, transitions: [ + .State1 => .State2, + ]) + } + + // tryEvent (twice) + machine <-! .Event0 <-! .Event1 + XCTAssertEqual(machine.state, MyState.State2) + } + + func testTryEvent_string() + { + let machine = Machine(state: .State0) + + // add 0 => 1 => 2 + machine.addRoutes(event: "Run", transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2, "Event=Run doesn't have 2 => Any") + } + + // https://github.com/ReactKit/SwiftState/issues/20 + func testTryEvent_issue20() + { + let machine = Machine(state: MyState.State2) { machine in + machine.addRoutes(event: .Event0, transitions: [.Any => .State0]) + } + + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0) + } + + // Fix for transitioning of routes w/ multiple from-states + // https://github.com/ReactKit/SwiftState/pull/32 + func testTryEvent_issue32() + { + let machine = Machine(state: .State0) { machine in + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + machine.addRoutes(event: .Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + } + + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State3) + } + + //-------------------------------------------------- + // MARK: - add/removeRoute + //-------------------------------------------------- + + func testAddRoute_multiple() + { + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + // add 2 => 1 => 0 + machine.addRoutes(event: .Event1, transitions: [ + .State2 => .State1, + .State1 => .State0, + ]) + } + + // initial + XCTAssertEqual(machine.state, MyState.State0) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0, "Event1 doesn't have 0 => Any.") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any.") + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0) + } + + func testAddRoute_handler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ], handler: { context in + invokeCount++ + return + }) + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testRemoveRoute() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // removeRoute + routeDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } + + //-------------------------------------------------- + // MARK: - add/removeHandler + //-------------------------------------------------- + + func testAddHandler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1) + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2) + + XCTAssertEqual(invokeCount, 2) + } + + func testRemoveHandler() + { + var invokeCount = 0 + + let machine = Machine(state: .State0) { machine in + + // add 0 => 1 => 2 + machine.addRoutes(event: .Event0, transitions: [ + .State0 => .State1, + .State1 => .State2, + ]) + + let handlerDisposable = machine.addHandler(event: .Event0) { context in + invokeCount++ + return + } + + // remove handler + handlerDisposable.dispose() + + } + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State1, "0 => 1 should be succesful") + + // tryEvent + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") + + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") + } +} \ No newline at end of file diff --git a/SwiftStateTests/BasicTests.swift b/Tests/MiscTests.swift similarity index 66% rename from SwiftStateTests/BasicTests.swift rename to Tests/MiscTests.swift index 0c2b9d3..b28ede7 100644 --- a/SwiftStateTests/BasicTests.swift +++ b/Tests/MiscTests.swift @@ -1,57 +1,20 @@ // -// BasicTests.swift +// MiscTests.swift // SwiftState // -// Created by Yasuhiro Inami on 2014/08/08. -// Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. +// Created by Yasuhiro Inami on 2015-12-05. +// Copyright © 2015 Yasuhiro Inami. All rights reserved. // import SwiftState import XCTest -class BasicTests: _TestCase +/// Unarranged tests. +class MiscTests: _TestCase { - func testREADME() - { - // setup state machine - let machine = Machine(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 - XCTAssertTrue(machine.state == .State0) - - // tryState 0 => 1 => 2 => 1 => 0 - - machine <- .State1 - XCTAssertTrue(machine.state == .State1) - - machine <- (.State2, "Hello") - XCTAssertTrue(machine.state == .State2) - - machine <- (.State1, "Bye") - XCTAssertTrue(machine.state == .State1) - - machine <- .State0 // fail: no 1 => 0 - XCTAssertTrue(machine.state == .State1) - } - func testREADME_string() { - let machine = Machine(state: ".State0") { machine in + let machine = StateMachine(state: ".State0") { machine in machine.addRoute(".State0" => ".State1") machine.addRoute(.Any => ".State2") { context in print("Any => 2, msg=\(context.userInfo)") } @@ -71,24 +34,24 @@ class BasicTests: _TestCase // tryState 0 => 1 => 2 => 1 => 0 machine <- ".State1" - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") machine <- (".State2", "Hello") - XCTAssertTrue(machine.state == ".State2") + XCTAssertEqual(machine.state, ".State2") machine <- (".State1", "Bye") - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") machine <- ".State0" // fail: no 1 => 0 - XCTAssertTrue(machine.state == ".State1") + XCTAssertEqual(machine.state, ".State1") print("machine.state = \(machine.state)") } // StateType + associated value - func testREADME_MyState2() + func testREADME_associatedValue() { - let machine = Machine(state: .State0("0")) { machine in + let machine = StateMachine(state: .State0("0")) { machine in machine.addRoute(.State0("0") => .State0("1")) machine.addRoute(.Any => .State0("2")) { context in print("Any => 2, msg=\(context.userInfo)") } @@ -108,24 +71,24 @@ class BasicTests: _TestCase // tryState 0 => 1 => 2 => 1 => 0 machine <- .State0("1") - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) machine <- (.State0("2"), "Hello") - XCTAssertTrue(machine.state == .State0("2")) + XCTAssertEqual(machine.state, MyState2.State0("2")) machine <- (.State0("1"), "Bye") - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) machine <- .State0("0") // fail: no 1 => 0 - XCTAssertTrue(machine.state == .State0("1")) + XCTAssertEqual(machine.state, MyState2.State0("1")) print("machine.state = \(machine.state)") } func testExample() { - let machine = Machine(state: .State0) { - + let machine = StateMachine(state: .State0) { + // add 0 => 1 $0.addRoute(.State0 => .State1) { context in print("[Transition 0=>1] \(context.fromState) => \(context.toState)") @@ -175,47 +138,57 @@ class BasicTests: _TestCase machine.configure { - // error + // add error handlers $0.addErrorHandler { context in print("[ERROR 1] \(context.fromState) => \(context.toState)") } - // entry - $0.addEntryHandler(.State0) { context in + // add entry handlers + $0.addHandler(.Any => .State0) { context in print("[Entry 0] \(context.fromState) => \(context.toState)") // NOTE: this should not be called } - $0.addEntryHandler(.State1) { context in + $0.addHandler(.Any => .State1) { context in print("[Entry 1] \(context.fromState) => \(context.toState)") } - $0.addEntryHandler(.State2) { context in + $0.addHandler(.Any => .State2) { context in print("[Entry 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - $0.addEntryHandler(.State2) { context in + $0.addHandler(.Any => .State2) { context in print("[Entry 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - // exit - $0.addExitHandler(.State0) { context in + // add exit handlers + $0.addHandler(.State0 => .Any) { context in print("[Exit 0] \(context.fromState) => \(context.toState)") } - $0.addExitHandler(.State1) { context in + $0.addHandler(.State1 => .Any) { context in print("[Exit 1] \(context.fromState) => \(context.toState)") } - $0.addExitHandler(.State2) { context in + $0.addHandler(.State2 => .Any) { context in print("[Exit 2] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } - $0.addExitHandler(.State2) { context in + $0.addHandler(.State2 => .Any) { context in print("[Exit 2b] \(context.fromState) => \(context.toState), userInfo = \(context.userInfo)") } } + XCTAssertEqual(machine.state, MyState.State0) + // tryState 0 => 1 => 2 => 1 => 0 => 3 - XCTAssertTrue(machine <- .State1) - XCTAssertTrue(machine <- (.State2, "State2 activate")) - XCTAssertTrue(machine <- (.State1, "State2 deactivate")) - XCTAssertTrue(machine <- .State0) - XCTAssertFalse(machine <- .State3) + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + + machine <- (.State2, "State2 activate") + XCTAssertEqual(machine.state, MyState.State2) + + machine <- (.State1, "State2 deactivate") + XCTAssertEqual(machine.state, MyState.State1) + + machine <- .State0 XCTAssertEqual(machine.state, MyState.State0) + + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State0, "No 0 => 3.") } } \ No newline at end of file diff --git a/SwiftStateTests/MyEvent.swift b/Tests/MyEvent.swift similarity index 100% rename from SwiftStateTests/MyEvent.swift rename to Tests/MyEvent.swift diff --git a/SwiftStateTests/MyState.swift b/Tests/MyState.swift similarity index 100% rename from SwiftStateTests/MyState.swift rename to Tests/MyState.swift diff --git a/SwiftStateTests/QiitaTests.swift b/Tests/QiitaTests.swift similarity index 93% rename from SwiftStateTests/QiitaTests.swift rename to Tests/QiitaTests.swift index 1f11e36..ee594bb 100644 --- a/SwiftStateTests/QiitaTests.swift +++ b/Tests/QiitaTests.swift @@ -25,7 +25,7 @@ class QiitaTests: _TestCase { var success = false - let machine = Machine(state: .None) { machine in + let machine = StateMachine(state: .None) { machine in // connect all states machine.addRoute(.Any => .Any) diff --git a/SwiftStateTests/RouteChainTests.swift b/Tests/RouteChainTests.swift similarity index 80% rename from SwiftStateTests/RouteChainTests.swift rename to Tests/RouteChainTests.swift index e1d144b..e258fea 100644 --- a/SwiftStateTests/RouteChainTests.swift +++ b/Tests/RouteChainTests.swift @@ -15,7 +15,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -49,7 +49,7 @@ class MachineChainTests: _TestCase var flag = false var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2, condition: { _ in flag }) { context in @@ -82,7 +82,7 @@ class MachineChainTests: _TestCase func testAddRouteChain_failBySkipping() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -97,7 +97,7 @@ class MachineChainTests: _TestCase func testAddRouteChain_failByHangingAround() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -117,7 +117,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 machine.addRouteChain(.State0 => .State1 => .State2) { context in @@ -140,7 +140,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 => 0 (back home) => 2 machine.addRouteChain(.State0 => .State1 => .State2 => .State0 => .State2) { context in @@ -164,7 +164,7 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .Any) // connect all states @@ -203,48 +203,25 @@ class MachineChainTests: _TestCase { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - let (routeChainID, _) = machine.addRouteChain(.State0 => .State1 => .State2) { context in + let chainDisposable = machine.addRouteChain(.State0 => .State1 => .State2) { context in invokeCount++ return } - // removeRouteChain - machine.removeRouteChain(routeChainID) - - } - - // tryState 0 => 1 - let success = machine <- .State1 - - XCTAssertFalse(success, "RouteChain should be removed.") - XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") - } - - func testRemoveChainHandler() - { - var invokeCount = 0 - - let machine = Machine(state: .State0) { machine in - - // add 0 => 1 => 2 - let (_, chainHandlerID) = machine.addRouteChain(.State0 => .State1 => .State2) { context in - invokeCount++ - return - } - - // removeHandler - machine.removeChainHandler(chainHandlerID) + // remove chain + chainDisposable.dispose() } // tryState 0 => 1 => 2 + machine <- .State1 - let success = machine <- .State2 + XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") - XCTAssertTrue(success, "0 => 1 => 2 should be successful.") + machine <- .State2 XCTAssertEqual(invokeCount, 0, "ChainHandler should NOT be performed.") } @@ -252,7 +229,7 @@ class MachineChainTests: _TestCase { var errorCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in let transitionChain = MyState.State0 => .State1 => .State2 @@ -287,20 +264,20 @@ class MachineChainTests: _TestCase { var errorCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in let transitionChain = MyState.State0 => .State1 => .State2 machine.addRoute(.Any => .Any) // connect all states // add 0 => 1 => 2 chainErrorHandler - let chainHandlerID = machine.addChainErrorHandler(transitionChain) { context in + let chainErrorHandlerDisposable = machine.addChainErrorHandler(transitionChain) { context in errorCount++ return } // remove chainErrorHandler - machine.removeChainHandler(chainHandlerID) + chainErrorHandlerDisposable.dispose() } diff --git a/SwiftStateTests/RouteMappingTests.swift b/Tests/RouteMappingTests.swift similarity index 86% rename from SwiftStateTests/RouteMappingTests.swift rename to Tests/RouteMappingTests.swift index ba1e14c..2a147bd 100644 --- a/SwiftStateTests/RouteMappingTests.swift +++ b/Tests/RouteMappingTests.swift @@ -78,7 +78,7 @@ class RouteMappingTests: _TestCase { var count = 0 - let machine = Machine<_State, _Event>(state: .Pending) { machine in + let machine = StateMachine<_State, _Event>(state: .Pending) { machine in machine.addRouteMapping { event, fromState, userInfo in // no routes for no event @@ -97,38 +97,38 @@ class RouteMappingTests: _TestCase } // increment `count` when any events i.e. `.CancelAction` and `.LoadAction(x)` succeed. - machine.addEventHandler(.Any) { event, transition, order, userInfo in + machine.addHandler(event: .Any) { event, transition, order, userInfo in count++ } } // initial - XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 0) // CancelAction (to .Pending state, same as before) machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 0, "`tryEvent()` failed, and `count` should not be incremented.") // LoadAction(1) (to .Loading(1) state) machine <-! .LoadAction(1) - XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(machine.state, _State.Loading(1)) XCTAssertEqual(count, 1) // LoadAction(1) (same as before) machine <-! .LoadAction(1) print(machine.state) - XCTAssertTrue(machine.state == .Loading(1)) + XCTAssertEqual(machine.state, _State.Loading(1)) XCTAssertEqual(count, 1, "`tryEvent()` failed, and `count` should not be incremented.") machine <-! .LoadAction(2) - XCTAssertTrue(machine.state == .Loading(2)) + XCTAssertEqual(machine.state, _State.Loading(2)) XCTAssertEqual(count, 2) machine <-! .CancelAction - XCTAssertTrue(machine.state == .Pending) + XCTAssertEqual(machine.state, _State.Pending) XCTAssertEqual(count, 3) } } diff --git a/SwiftStateTests/RouteTests.swift b/Tests/RouteTests.swift similarity index 100% rename from SwiftStateTests/RouteTests.swift rename to Tests/RouteTests.swift diff --git a/SwiftStateTests/TryEventTests.swift b/Tests/StateMachineEventTests.swift similarity index 59% rename from SwiftStateTests/TryEventTests.swift rename to Tests/StateMachineEventTests.swift index c443650..9f85b98 100644 --- a/SwiftStateTests/TryEventTests.swift +++ b/Tests/StateMachineEventTests.swift @@ -1,5 +1,5 @@ // -// TryEventTests.swift +// StateMachineEventTests.swift // SwiftState // // Created by Yasuhiro Inami on 2014/08/05. @@ -9,15 +9,15 @@ import SwiftState import XCTest -class TryEventTests: _TestCase +class StateMachineEventTests: _TestCase { func testCanTryEvent() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -31,10 +31,10 @@ class TryEventTests: _TestCase func testTryEvent() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -48,17 +48,16 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryEvent - let success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event0 doesn't have 2 => Any") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any") } func testTryEvent_string() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 => 2 - machine.addRouteEvent("Run", transitions: [ + machine.addRoutes(event: "Run", transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -72,19 +71,19 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryEvent - let success = machine <-! "Run" - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event=Run doesn't have 2 => Any") + machine <-! "Run" + XCTAssertEqual(machine.state, MyState.State2, "Event=Run doesn't have 2 => Any") } // https://github.com/ReactKit/SwiftState/issues/20 func testTryEvent_issue20() { - let machine = Machine(state: MyState.State2) { machine in - machine.addRouteEvent(.Event0, transitions: [.Any => .State0]) + let machine = StateMachine(state: MyState.State2) { machine in + machine.addRoutes(event: .Event0, transitions: [.Any => .State0]) } - XCTAssertTrue(machine <-! .Event0) + // tryEvent + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State0) } @@ -93,22 +92,25 @@ class TryEventTests: _TestCase { var eventCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Event0, transitions: [.Any => .Any]) { _ in + machine.addRoutes(event: .Event0, transitions: [.Any => .Any]) { _ in eventCount++ } } XCTAssertEqual(eventCount, 0) + // tryEvent machine <-! .Event0 XCTAssertEqual(eventCount, 1) XCTAssertEqual(machine.state, MyState.State0, "State should NOT be changed") + // tryEvent machine <- .State1 XCTAssertEqual(machine.state, MyState.State1, "State should be changed") + // tryEvent machine <-! .Event0 XCTAssertEqual(eventCount, 2) XCTAssertEqual(machine.state, MyState.State1, "State should NOT be changed") @@ -118,16 +120,18 @@ class TryEventTests: _TestCase // https://github.com/ReactKit/SwiftState/pull/32 func testTryEvent_issue32() { - let machine = Machine(state: .State0) { machine in - machine.addRouteEvent(.Event0, transitions: [ .State0 => .State1 ]) - machine.addRouteEvent(.Event1, routes: [ [ .State1, .State2 ] => .State3 ]) + let machine = StateMachine(state: .State0) { machine in + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1 ]) + machine.addRoutes(event: .Event1, routes: [ [ .State1, .State2 ] => .State3 ]) } XCTAssertEqual(machine.state, MyState.State0) + // tryEvent machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) + // tryEvent machine <-! .Event1 XCTAssertEqual(machine.state, MyState.State3) } @@ -137,22 +141,22 @@ class TryEventTests: _TestCase func testHasRoute_anyEvent() { ({ - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Any, transitions: [.State0 => .State1]) + machine.addRoutes(event: .Any, transitions: [.State0 => .State1]) } - let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) XCTAssertTrue(hasRoute) })() ({ - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) - machine.addRouteEvent(.Any, transitions: [.State2 => .State3]) + machine.addRoutes(event: .Any, transitions: [.State2 => .State3]) } - let hasRoute = machine.hasRoute(.State0 => .State1, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State0 => .State1) XCTAssertFalse(hasRoute) })() } @@ -161,26 +165,26 @@ class TryEventTests: _TestCase // https://github.com/ReactKit/SwiftState/pull/19 func testHasRoute_issue19() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) // no-event - machine.addRouteEvent(.Event0, transitions: [.State1 => .State2]) // with-event + machine.addRoutes(event: .Event0, transitions: [.State1 => .State2]) // with-event } - let hasRoute = machine.hasRoute(.State1 => .State2, forEvent: .Event0) + let hasRoute = machine.hasRoute(event: .Event0, transition: .State1 => .State2) XCTAssertTrue(hasRoute) } //-------------------------------------------------- - // MARK: - add/removeRouteEvent + // MARK: - add/removeRoute //-------------------------------------------------- - func testAddRouteEvent_tryState() + func testAddRoute_tryState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 & 1 => 2 // (NOTE: this is not chaining e.g. 0 => 1 => 2) - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) @@ -196,69 +200,63 @@ class TryEventTests: _TestCase XCTAssertEqual(machine.state, MyState.State2) // tryState 2 => 3 - let success = machine <- .State3 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "2 => 3 is not registered.") + machine <- .State3 + XCTAssertEqual(machine.state, MyState.State2, "2 => 3 is not registered.") } - func testAddRouteEvent_multiple() + func testAddRoute_multiple() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) // add 2 => 1 => 0 - machine.addRouteEvent(.Event1, transitions: [ + machine.addRoutes(event: .Event1, transitions: [ .State2 => .State1, .State1 => .State0, ]) } - var success: Bool + // initial + XCTAssertEqual(machine.state, MyState.State0) // tryEvent - success = machine <-! .Event1 - XCTAssertEqual(machine.state, MyState.State0) - XCTAssertFalse(success, "Event1 doesn't have 0 => Any.") + machine <-! .Event1 + XCTAssertEqual(machine.state, MyState.State0, "Event1 doesn't have 0 => Any.") // tryEvent - success = machine <-! .Event0 + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) // tryEvent - success = machine <-! .Event0 + machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2) - XCTAssertTrue(success) // tryEvent - success = machine <-! .Event0 - XCTAssertEqual(machine.state, MyState.State2) - XCTAssertFalse(success, "Event0 doesn't have 2 => Any.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State2, "Event0 doesn't have 2 => Any.") // tryEvent - success = machine <-! .Event1 + machine <-! .Event1 XCTAssertEqual(machine.state, MyState.State1) - XCTAssertTrue(success) // tryEvent - success = machine <-! .Event1 + machine <-! .Event1 XCTAssertEqual(machine.state, MyState.State0) - XCTAssertTrue(success) } - func testAddRouteEvent_handler() + func testAddRoute_handler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ], handler: { context in @@ -278,58 +276,55 @@ class TryEventTests: _TestCase XCTAssertEqual(invokeCount, 2) } - func testRemoveRouteEvent() + func testRemoveRoute() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - let routeIDs = machine.addRouteEvent(.Event0, transitions: [ + let routeDisposable = machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - machine.addEventHandler(.Event0) { context in + machine.addHandler(event: .Event0) { context in invokeCount++ return } // removeRoute - for routeID in routeIDs { - machine.removeRoute(routeID) - } + routeDisposable.dispose() } - // tryEvent - var success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") + // initial + XCTAssertEqual(machine.state, MyState.State0) // tryEvent - success = machine <-! .Event0 - XCTAssertFalse(success, "RouteEvent should be removed.") + machine <-! .Event0 + XCTAssertEqual(machine.state, MyState.State0, "Route should be removed.") - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") } //-------------------------------------------------- - // MARK: - add/removeEventHandler + // MARK: - add/removeHandler //-------------------------------------------------- - func testAddEventHandler() + func testAddHandler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - machine.addEventHandler(.Event0) { context in + machine.addHandler(event: .Event0) { context in invokeCount++ return } @@ -347,25 +342,25 @@ class TryEventTests: _TestCase XCTAssertEqual(invokeCount, 2) } - func testRemoveEventHandler() + func testRemoveHandler() { var invokeCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 => 2 - machine.addRouteEvent(.Event0, transitions: [ + machine.addRoutes(event: .Event0, transitions: [ .State0 => .State1, .State1 => .State2, ]) - let handlerID = machine.addEventHandler(.Event0) { context in + let handlerDisposable = machine.addHandler(event: .Event0) { context in invokeCount++ return } - // removeHandler - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -377,6 +372,6 @@ class TryEventTests: _TestCase machine <-! .Event0 XCTAssertEqual(machine.state, MyState.State2, "1 => 2 should be succesful") - XCTAssertEqual(invokeCount, 0, "EventHandler should NOT be performed") + XCTAssertEqual(invokeCount, 0, "Handler should NOT be performed") } } \ No newline at end of file diff --git a/SwiftStateTests/MachineTests.swift b/Tests/StateMachineTests.swift similarity index 72% rename from SwiftStateTests/MachineTests.swift rename to Tests/StateMachineTests.swift index da3b0e0..1407f03 100644 --- a/SwiftStateTests/MachineTests.swift +++ b/Tests/StateMachineTests.swift @@ -1,6 +1,6 @@ // -// MachineTests.swift -// MachineTests +// StateMachineTests.swift +// SwiftState // // Created by Yasuhiro Inami on 2014/08/03. // Copyright (c) 2014年 Yasuhiro Inami. All rights reserved. @@ -9,15 +9,54 @@ import SwiftState import XCTest -class MachineTests: _TestCase +class StateMachineTests: _TestCase { func testInit() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) XCTAssertEqual(machine.state, MyState.State0) } + //-------------------------------------------------- + // MARK: - tryState a.k.a `<-` + //-------------------------------------------------- + + // machine <- state + func testTryState() + { + let machine = StateMachine(state: .State0) + + // tryState 0 => 1, without registering any transitions + machine <- .State1 + + XCTAssertEqual(machine.state, MyState.State0, "0 => 1 should fail because transition is not added yet.") + + // add 0 => 1 + machine.addRoute(.State0 => .State1) + + // tryState 0 => 1 + machine <- .State1 + XCTAssertEqual(machine.state, MyState.State1) + } + + func testTryState_string() + { + let machine = StateMachine(state: "0") + + // tryState 0 => 1, without registering any transitions + machine <- "1" + + XCTAssertEqual(machine.state, "0", "0 => 1 should fail because transition is not added yet.") + + // add 0 => 1 + machine.addRoute("0" => "1") + + // tryState 0 => 1 + machine <- "1" + XCTAssertEqual(machine.state, "1") + } + //-------------------------------------------------- // MARK: - addRoute //-------------------------------------------------- @@ -25,7 +64,7 @@ class MachineTests: _TestCase // add state1 => state2 func testAddRoute() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) } @@ -40,7 +79,7 @@ class MachineTests: _TestCase // add .Any => state func testAddRoute_fromAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .State1) // Any => State1 } @@ -55,7 +94,7 @@ class MachineTests: _TestCase // add state => .Any func testAddRoute_toAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State1 => .Any) // State1 => Any } @@ -70,7 +109,7 @@ class MachineTests: _TestCase // add .Any => .Any func testAddRoute_bothAnyState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.Any => .Any) // Any => Any } @@ -85,14 +124,11 @@ class MachineTests: _TestCase // add state0 => state0 func testAddRoute_sameState() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State0) } XCTAssertTrue(machine.hasRoute(.State0 => .State0)) - - // tryState 0 => 0 - XCTAssertTrue(machine <- .State0) } // add route + condition @@ -100,7 +136,7 @@ class MachineTests: _TestCase { var flag = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1, condition: { _ in flag }) @@ -116,7 +152,7 @@ class MachineTests: _TestCase // add route + condition + blacklist func testAddRoute_condition_blacklist() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => Any, except 0 => 2 machine.addRoute(.State0 => .Any, condition: { context in return context.toState != .State2 @@ -134,7 +170,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) { context in XCTAssertEqual(context.fromState, MyState.State0) @@ -159,7 +195,7 @@ class MachineTests: _TestCase var invokedCount = 0 var flag = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 without condition to guarantee 0 => 1 transition machine.addRoute(.State0 => .State1) @@ -202,7 +238,7 @@ class MachineTests: _TestCase func testAddRoute_array_left() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 2 or 1 => 2 machine.addRoute([.State0, .State1] => .State2) } @@ -217,7 +253,7 @@ class MachineTests: _TestCase func testAddRoute_array_right() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 or 0 => 2 machine.addRoute(.State0 => [.State1, .State2]) } @@ -232,7 +268,7 @@ class MachineTests: _TestCase func testAddRoute_array_both() { - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 2 or 0 => 3 or 1 => 2 or 1 => 3 machine.addRoute([MyState.State0, MyState.State1] => [MyState.State2, MyState.State3]) } @@ -261,65 +297,16 @@ class MachineTests: _TestCase func testRemoveRoute() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) - let routeID = machine.addRoute(.State0 => .State1) + let routeDisposable = machine.addRoute(.State0 => .State1) XCTAssertTrue(machine.hasRoute(.State0 => .State1)) - var success: Bool - success = machine.removeRoute(routeID) + // remove route + routeDisposable.dispose() - XCTAssertTrue(success) XCTAssertFalse(machine.hasRoute(.State0 => .State1)) - - // fails removing already unregistered route - success = machine.removeRoute(routeID) - - XCTAssertFalse(success) - } - - //-------------------------------------------------- - // MARK: - tryState a.k.a `<-` - //-------------------------------------------------- - - // machine <- state - func testTryState() - { - let machine = Machine(state: .State0) - - // tryState 0 => 1, without registering any transitions - machine <- .State1 - - XCTAssertEqual(machine.state, MyState.State0, "0 => 1 should fail because transition is not added yet.") - - // add 0 => 1 - machine.addRoute(.State0 => .State1) - - // tryState 0 => 1, returning flag - let success = machine <- .State1 - - XCTAssertTrue(success) - XCTAssertEqual(machine.state, MyState.State1) - } - - func testTryState_string() - { - let machine = Machine(state: "0") - - // tryState 0 => 1, without registering any transitions - machine <- "1" - - XCTAssertEqual(machine.state, "0", "0 => 1 should fail because transition is not added yet.") - - // add 0 => 1 - machine.addRoute("0" => "1") - - // tryState 0 => 1, returning flag - let success = machine <- "1" - - XCTAssertTrue(success) - XCTAssertEqual(machine.state, "1") } //-------------------------------------------------- @@ -330,7 +317,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -357,7 +344,7 @@ class MachineTests: _TestCase { var invokedCount = 0 - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -398,7 +385,7 @@ class MachineTests: _TestCase var passed1 = false var passed2 = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) @@ -427,7 +414,7 @@ class MachineTests: _TestCase { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in machine.addRoute(.State0 => .State1) @@ -456,12 +443,12 @@ class MachineTests: _TestCase { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addHandler(.State0 => .State1) { context in + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in XCTFail("Should never reach here") } @@ -472,7 +459,8 @@ class MachineTests: _TestCase passed = true } - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -486,32 +474,34 @@ class MachineTests: _TestCase func testRemoveHandler_unregistered() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addHandler(.State0 => .State1) { context in + let handlerDisposable = machine.addHandler(.State0 => .State1) { context in // empty } + XCTAssertFalse(handlerDisposable.disposed) + // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) + handlerDisposable.dispose() // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") } func testRemoveErrorHandler() { var passed = false - let machine = Machine(state: .State0) { machine in + let machine = StateMachine(state: .State0) { machine in // add 2 => 1 machine.addRoute(.State2 => .State1) - let handlerID = machine.addErrorHandler { context in + let handlerDisposable = machine.addErrorHandler { context in XCTFail("Should never reach here") } @@ -522,7 +512,8 @@ class MachineTests: _TestCase passed = true } - machine.removeHandler(handlerID) + // remove handler + handlerDisposable.dispose() } @@ -534,19 +525,78 @@ class MachineTests: _TestCase func testRemoveErrorHandler_unregistered() { - let machine = Machine(state: .State0) + let machine = StateMachine(state: .State0) // add 0 => 1 machine.addRoute(.State0 => .State1) - let handlerID = machine.addErrorHandler { context in + let handlerDisposable = machine.addErrorHandler { context in // empty } + XCTAssertFalse(handlerDisposable.disposed) + // remove handler - XCTAssertTrue(machine.removeHandler(handlerID)) + handlerDisposable.dispose() // remove already unregistered handler - XCTAssertFalse(machine.removeHandler(handlerID), "removeHandler should fail because handler is already removed.") + XCTAssertTrue(handlerDisposable.disposed, "removeHandler should fail because handler is already removed.") + } + + //-------------------------------------------------- + // MARK: - StateRouteMapping + //-------------------------------------------------- + + func testStateRouteMapping() + { + var routeMappingDisposable: Disposable? + + let machine = StateMachine(state: .State0) { machine in + + // add 0 => 1 & 0 => 2 (using `StateRouteMapping` closure) + routeMappingDisposable = machine.addRouteMapping { fromState, userInfo -> [MyState]? in + if fromState == .State0 { + return [.State1, .State2] + } + else { + return nil + } + } + + // add 1 => 0 (can also use `EventRouteMapping` closure for single-`toState`) + machine.addRouteMapping { event, fromState, userInfo -> MyState? in + guard event == nil else { return nil } + + if fromState == .State1 { + return .State0 + } + else { + return nil + } + } + } + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertTrue(machine.hasRoute(.State0 => .State1)) + XCTAssertTrue(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) + + // remove routeMapping + routeMappingDisposable?.dispose() + + XCTAssertFalse(machine.hasRoute(.State0 => .State0)) + XCTAssertFalse(machine.hasRoute(.State0 => .State1)) + XCTAssertFalse(machine.hasRoute(.State0 => .State2)) + XCTAssertTrue(machine.hasRoute(.State1 => .State0)) + XCTAssertFalse(machine.hasRoute(.State1 => .State1)) + XCTAssertFalse(machine.hasRoute(.State1 => .State2)) + XCTAssertFalse(machine.hasRoute(.State2 => .State0)) + XCTAssertFalse(machine.hasRoute(.State2 => .State1)) + XCTAssertFalse(machine.hasRoute(.State2 => .State2)) } } diff --git a/SwiftStateTests/String+TestExt.swift b/Tests/String+TestExt.swift similarity index 100% rename from SwiftStateTests/String+TestExt.swift rename to Tests/String+TestExt.swift diff --git a/SwiftStateTests/TransitionChainTests.swift b/Tests/TransitionChainTests.swift similarity index 100% rename from SwiftStateTests/TransitionChainTests.swift rename to Tests/TransitionChainTests.swift diff --git a/SwiftStateTests/TransitionTests.swift b/Tests/TransitionTests.swift similarity index 100% rename from SwiftStateTests/TransitionTests.swift rename to Tests/TransitionTests.swift diff --git a/SwiftStateTests/_TestCase.swift b/Tests/_TestCase.swift similarity index 100% rename from SwiftStateTests/_TestCase.swift rename to Tests/_TestCase.swift