diff --git a/StateMachine/GraphableStateMachineSchema.swift b/StateMachine/GraphableStateMachineSchema.swift index b281add..0b59f95 100644 --- a/StateMachine/GraphableStateMachineSchema.swift +++ b/StateMachine/GraphableStateMachineSchema.swift @@ -47,10 +47,10 @@ public struct GraphableStateMachineSchema: public typealias Subject = C public let initialState: State - public let transitionLogic: (State, Event) -> (State, (Subject -> ())?)? + public let transitionLogic: (State, Event) -> (State, ((Subject) -> ())?)? public let DOTDigraph: String - public init(initialState: State, transitionLogic: (State, Event) -> (State, (Subject -> ())?)?) { + public init(initialState: State, transitionLogic: @escaping (State, Event) -> (State, ((Subject) -> ())?)?) { self.initialState = initialState self.transitionLogic = transitionLogic self.DOTDigraph = GraphableStateMachineSchema.DOTDigraphGivenInitialState(initialState, transitionLogic: transitionLogic) @@ -84,24 +84,24 @@ public struct GraphableStateMachineSchema: /// /// [DOT]: http://en.wikipedia.org/wiki/DOT_%28graph_description_language%29 /// [Graphviz]: http://www.graphviz.org/ - public func saveDOTDigraphIfRunningInSimulator(filepathRelativeToCurrentFile filepathRelativeToCurrentFile: String, file: String = #file) throws { + public func saveDOTDigraphIfRunningInSimulator(filepathRelativeToCurrentFile: String, file: String = #file) throws { if TARGET_IPHONE_SIMULATOR == 1 { - let filepath = ((file as NSString).stringByDeletingLastPathComponent as NSString).stringByAppendingPathComponent(filepathRelativeToCurrentFile) - try DOTDigraph.writeToFile(filepath, atomically: true, encoding: NSUTF8StringEncoding) + let filepath = ((file as NSString).deletingLastPathComponent as NSString).appendingPathComponent(filepathRelativeToCurrentFile) + try DOTDigraph.write(toFile: filepath, atomically: true, encoding: String.Encoding.utf8) } } #endif - private static func DOTDigraphGivenInitialState(initialState: State, transitionLogic: (State, Event) -> (State, (Subject -> ())?)?) -> String { + fileprivate static func DOTDigraphGivenInitialState(_ initialState: State, transitionLogic: (State, Event) -> (State, ((Subject) -> ())?)?) -> String { let states = State.DOTLabelableItems let events = Event.DOTLabelableItems var stateIndexesByLabel: [String: Int] = [:] - for (i, state) in states.enumerate() { + for (i, state) in states.enumerated() { stateIndexesByLabel[label(state)] = i + 1 } - func index(state: State) -> Int { + func index(_ state: State) -> Int { return stateIndexesByLabel[label(state)]! } @@ -129,6 +129,6 @@ public struct GraphableStateMachineSchema: /// Helper function used when generating DOT digraph strings. -private func label(x: T) -> String { - return x.DOTLabel.stringByReplacingOccurrencesOfString("\"", withString: "\\\"", options: .LiteralSearch, range: nil) +private func label(_ x: T) -> String { + return x.DOTLabel.replacingOccurrences(of: "\"", with: "\\\"", options: .literal, range: nil) } diff --git a/StateMachine/StateMachine.swift b/StateMachine/StateMachine.swift index 5147499..c2e5540 100644 --- a/StateMachine/StateMachine.swift +++ b/StateMachine/StateMachine.swift @@ -24,23 +24,24 @@ public protocol StateMachineSchemaType { associatedtype Subject var initialState: State { get } - var transitionLogic: (State, Event) -> (State, (Subject -> ())?)? { get } + var transitionLogic: (State, Event) -> (State, ((Subject) -> ())?)? { get } - init(initialState: State, transitionLogic: (State, Event) -> (State, (Subject -> ())?)?) + init(initialState: State, transitionLogic: @escaping (State, Event) -> (State, ((Subject) -> ())?)?) } /// A state machine schema conforming to the `StateMachineSchemaType` /// protocol. See protocol documentation for more information. public struct StateMachineSchema: StateMachineSchemaType { + public typealias State = A public typealias Event = B public typealias Subject = C public let initialState: State - public let transitionLogic: (State, Event) -> (State, (Subject -> ())?)? + public let transitionLogic: (State, Event) -> (State, ((Subject) -> ())?)? - public init(initialState: State, transitionLogic: (State, Event) -> (State, (Subject -> ())?)?) { + public init(initialState: State, transitionLogic: @escaping (State, Event) -> (State, ((Subject) -> ())?)?) { self.initialState = initialState self.transitionLogic = transitionLogic } @@ -66,7 +67,7 @@ public struct StateMachineSchema: StateMachineSchemaType { /// and the state after the transition. public final class StateMachine { /// The current state of the machine. - public private(set) var state: Schema.State + public fileprivate(set) var state: Schema.State /// An optional block called after a transition with three arguments: /// the state before the transition, the event causing the transition, @@ -75,13 +76,13 @@ public final class StateMachine { /// The schema of the state machine. See `StateMachineSchemaType` /// documentation for more information. - private let schema: Schema + fileprivate let schema: Schema /// Object associated with the state machine. Can be accessed in /// transition blocks. Closure used to allow for weak references. - private let subject: () -> Schema.Subject? + fileprivate let subject: () -> Schema.Subject? - private init(schema: Schema, subject: () -> Schema.Subject?) { + fileprivate init(schema: Schema, subject: @escaping () -> Schema.Subject?) { self.state = schema.initialState self.schema = schema self.subject = subject @@ -92,10 +93,10 @@ public final class StateMachine { /// becomes `nil`. If the transition logic of the schema defines a transition /// for current state and given event, the state is changed, the optional /// transition block is executed, and `didTransitionCallback` is called. - public func handleEvent(event: Schema.Event) { + public func handleEvent(_ event: Schema.Event) { guard let subject = subject(), - (newState, transition) = schema.transitionLogic(state, event) + let (newState, transition) = schema.transitionLogic(state, event) else { return } diff --git a/StateMachineTests/StateMachineSpec.swift b/StateMachineTests/StateMachineSpec.swift index 28bda10..90c64f4 100644 --- a/StateMachineTests/StateMachineSpec.swift +++ b/StateMachineTests/StateMachineSpec.swift @@ -10,45 +10,45 @@ private struct NumberKeeper { private enum Number { - case One, Two, Three + case one, two, three } private enum Operation { - case Increment, Decrement + case increment, decrement } extension Number: DOTLabelable { static var DOTLabelableItems: [Number] { - return [.One, .Two, .Three] + return [.one, .two, .three] } } extension Operation: DOTLabelable { static var DOTLabelableItems: [Operation] { - return [.Increment, .Decrement] + return [.increment, .decrement] } } -private enum SimpleState { case S1, S2 } -private enum SimpleEvent { case E } +private enum SimpleState { case s1, s2 } +private enum SimpleEvent { case e } -private func createSimpleSchema(forward forward: (T -> ())? = nil, backward: (T -> ())? = nil) -> StateMachineSchema { - return StateMachineSchema(initialState: .S1) { (state, event) in +private func createSimpleSchema(forward: ((T) -> ())? = nil, backward: ((T) -> ())? = nil) -> StateMachineSchema { + return StateMachineSchema(initialState: .s1) { (state, event) in switch state { - case .S1: switch event { - case .E: return (.S2, { forward?($0) }) + case .s1: switch event { + case .e: return (.s2, { forward?($0) }) } - case .S2: switch event { - case .E: return (.S1, { backward?($0) }) + case .s2: switch event { + case .e: return (.s1, { backward?($0) }) } } } } -private func createSimpleMachine(forward forward: (() -> ())? = nil, backward: (() -> ())? = nil) -> StateMachine> { +private func createSimpleMachine(forward: (() -> ())? = nil, backward: (() -> ())? = nil) -> StateMachine> { return StateMachine(schema: createSimpleSchema(forward: { _ in forward?() }, backward: { _ in backward?() }), subject: ()) } @@ -75,25 +75,25 @@ class StateMachineSpec: QuickSpec { beforeEach { keeper = NumberKeeper(n: 1) - - let schema: StateMachineSchema = StateMachineSchema(initialState: .One) { (state, event) in - let decrement: NumberKeeper -> () = { _ in keeper.n -= 1 } - let increment: NumberKeeper -> () = { _ in keeper.n += 1 } + + let schema = StateMachineSchema(initialState: .one) { (state, event) in + let decrement: (NumberKeeper) -> () = { _ in keeper.n -= 1 } + let increment: (NumberKeeper) -> () = { _ in keeper.n += 1 } switch state { - case .One: switch event { - case .Decrement: return nil - case .Increment: return (.Two, increment) + case .one: switch event { + case .decrement: return nil + case .increment: return (.two, increment) } - case .Two: switch event { - case .Decrement: return (.One, decrement) - case .Increment: return (.Three, increment) + case .two: switch event { + case .decrement: return (.one, decrement) + case .increment: return (.three, increment) } - case .Three: switch event { - case .Decrement: return (.Two, decrement) - case .Increment: return nil + case .three: switch event { + case .decrement: return (.two, decrement) + case .increment: return nil } } } @@ -103,28 +103,28 @@ class StateMachineSpec: QuickSpec { it("can be associated with a subject") { expect(keeper.n) == 1 - keeperMachine.handleEvent(.Increment) + keeperMachine.handleEvent(.increment) expect(keeper.n) == 2 } it("doesn't have to be associated with a subject") { let machine = createSimpleMachine() - expect(machine.state) == SimpleState.S1 - machine.handleEvent(.E) - expect(machine.state) == SimpleState.S2 + expect(machine.state) == SimpleState.s1 + machine.handleEvent(.e) + expect(machine.state) == SimpleState.s2 } it("changes state on correct event") { - expect(keeperMachine.state) == Number.One - keeperMachine.handleEvent(.Increment) - expect(keeperMachine.state) == Number.Two + expect(keeperMachine.state) == Number.one + keeperMachine.handleEvent(.increment) + expect(keeperMachine.state) == Number.two } it("doesn't change state on ignored event") { - expect(keeperMachine.state) == Number.One - keeperMachine.handleEvent(.Decrement) - expect(keeperMachine.state) == Number.One + expect(keeperMachine.state) == Number.one + keeperMachine.handleEvent(.decrement) + expect(keeperMachine.state) == Number.one } it("executes transition block on transition") { @@ -133,7 +133,7 @@ class StateMachineSpec: QuickSpec { let machine = createSimpleMachine(forward: { didExecuteBlock = true }) expect(didExecuteBlock) == false - machine.handleEvent(.E) + machine.handleEvent(.e) expect(didExecuteBlock) == true } @@ -142,20 +142,20 @@ class StateMachineSpec: QuickSpec { var callbackWasCalledCorrectly = false machine.didTransitionCallback = { (oldState: SimpleState, event: SimpleEvent, newState: SimpleState) in - callbackWasCalledCorrectly = oldState == .S1 && event == .E && newState == .S2 + callbackWasCalledCorrectly = oldState == .s1 && event == .e && newState == .s2 } - machine.handleEvent(.E) + machine.handleEvent(.e) expect(callbackWasCalledCorrectly) == true } it("can trigger transition from within transition") { let subject = Subject(schema: createSimpleSchema(forward: { - $0.machine.handleEvent(.E) + $0.machine.handleEvent(.e) })) - subject.machine.handleEvent(.E) - expect(subject.machine.state) == SimpleState.S1 + subject.machine.handleEvent(.e) + expect(subject.machine.state) == SimpleState.s1 } it("doesn't cause machine-subject reference cycles") { @@ -164,7 +164,7 @@ class StateMachineSpec: QuickSpec { init() { machine = StateMachine( - schema: StateMachineSchema(initialState: .S1) { _ in nil }, + schema: StateMachineSchema(initialState: .s1) { _ in nil }, subject: self) } } @@ -179,32 +179,32 @@ class StateMachineSpec: QuickSpec { describe("Graphable State Machine") { it("has representation in DOT format") { - let schema: GraphableStateMachineSchema = GraphableStateMachineSchema(initialState: .One) { (state, event) in + let schema: GraphableStateMachineSchema = GraphableStateMachineSchema(initialState: .one) { (state, event) in switch state { - case .One: switch event { - case .Decrement: return nil - case .Increment: return (.Two, nil) + case .one: switch event { + case .decrement: return nil + case .increment: return (.two, nil) } - case .Two: switch event { - case .Decrement: return (.One, nil) - case .Increment: return (.Three, nil) + case .two: switch event { + case .decrement: return (.one, nil) + case .increment: return (.three, nil) } - case .Three: switch event { - case .Decrement: return (.Two, nil) - case .Increment: return nil + case .three: switch event { + case .decrement: return (.two, nil) + case .increment: return nil } } } - expect(schema.DOTDigraph) == "digraph {\n graph [rankdir=LR]\n\n 0 [label=\"\", shape=plaintext]\n 0 -> 1 [label=\"START\"]\n\n 1 [label=\"One\"]\n 2 [label=\"Two\"]\n 3 [label=\"Three\"]\n\n 1 -> 2 [label=\"Increment\"]\n 2 -> 3 [label=\"Increment\"]\n 2 -> 1 [label=\"Decrement\"]\n 3 -> 2 [label=\"Decrement\"]\n}" + expect(schema.DOTDigraph) == "digraph {\n graph [rankdir=LR]\n\n 0 [label=\"\", shape=plaintext]\n 0 -> 1 [label=\"START\"]\n\n 1 [label=\"one\"]\n 2 [label=\"two\"]\n 3 [label=\"three\"]\n\n 1 -> 2 [label=\"increment\"]\n 2 -> 3 [label=\"increment\"]\n 2 -> 1 [label=\"decrement\"]\n 3 -> 2 [label=\"decrement\"]\n}" } it("escapes double quotes in labels") { - let schema = GraphableStateMachineSchema(initialState: .S) { _ in - (.S, nil) + let schema = GraphableStateMachineSchema(initialState: .s) { _ in + (.s, nil) } expect(schema.DOTDigraph) == "digraph {\n graph [rankdir=LR]\n\n 0 [label=\"\", shape=plaintext]\n 0 -> 1 [label=\"START\"]\n\n 1 [label=\"An \\\"awesome\\\" state\"]\n\n 1 -> 1 [label=\"An \\\"awesome\\\" event\"]\n}" @@ -216,13 +216,13 @@ class StateMachineSpec: QuickSpec { enum State: DOTLabelable { - case S + case s var DOTLabel: String { return "An \"awesome\" state" } - static var DOTLabelableItems: [State] { return [.S] } + static var DOTLabelableItems: [State] { return [.s] } } enum Event: DOTLabelable { - case E + case e var DOTLabel: String { return "An \"awesome\" event" } - static var DOTLabelableItems: [Event] { return [.E] } + static var DOTLabelableItems: [Event] { return [.e] } } diff --git a/SwiftyStateMachine.podspec b/SwiftyStateMachine.podspec index 07be261..217629b 100644 --- a/SwiftyStateMachine.podspec +++ b/SwiftyStateMachine.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "SwiftyStateMachine" - s.version = "0.3.0" + s.version = "0.4.0" s.summary = "A Swift µframework for creating finite-state machines, designed for clarity and maintainability." s.homepage = "https://github.com/macoscope/SwiftyStateMachine" s.license = "MIT" diff --git a/SwiftyStateMachine.xcodeproj/project.pbxproj b/SwiftyStateMachine.xcodeproj/project.pbxproj index a26bfcd..f02e812 100644 --- a/SwiftyStateMachine.xcodeproj/project.pbxproj +++ b/SwiftyStateMachine.xcodeproj/project.pbxproj @@ -314,20 +314,24 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0820; ORGANIZATIONNAME = Macoscope; TargetAttributes = { CB4D56981AC169A3006715E8 = { CreatedOnToolsVersion = 6.3; + LastSwiftMigration = 0820; }; CB4D56A31AC169A3006715E8 = { CreatedOnToolsVersion = 6.3; + LastSwiftMigration = 0820; }; CBE36D081AC44BA000CFE10F = { CreatedOnToolsVersion = 6.3; + LastSwiftMigration = 0830; }; CBE36D121AC44BA000CFE10F = { CreatedOnToolsVersion = 6.3; + LastSwiftMigration = 0830; }; }; }; @@ -446,8 +450,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -495,8 +501,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -516,6 +524,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.3; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; @@ -527,6 +536,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -539,6 +549,7 @@ PRODUCT_NAME = SwiftyStateMachine; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -546,6 +557,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -557,6 +569,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.macoscope.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftyStateMachine; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -576,6 +589,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.macoscope.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftyStateMachineTests; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -591,6 +605,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "net.macoscope.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftyStateMachineTests; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -616,6 +631,7 @@ PRODUCT_NAME = SwiftyStateMachine; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -636,6 +652,7 @@ PRODUCT_NAME = SwiftyStateMachine; SDKROOT = macosx; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -659,6 +676,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.macoscope.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftyStateMachineTests; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -677,6 +695,7 @@ PRODUCT_BUNDLE_IDENTIFIER = "net.macoscope.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = SwiftyStateMachineTests; SDKROOT = macosx; + SWIFT_VERSION = 3.0; }; name = Release; }; diff --git a/SwiftyStateMachine.xcodeproj/xcshareddata/xcschemes/SwiftyStateMachine-Mac.xcscheme b/SwiftyStateMachine.xcodeproj/xcshareddata/xcschemes/SwiftyStateMachine-Mac.xcscheme index b80f30e..93de6fc 100644 --- a/SwiftyStateMachine.xcodeproj/xcshareddata/xcschemes/SwiftyStateMachine-Mac.xcscheme +++ b/SwiftyStateMachine.xcodeproj/xcshareddata/xcschemes/SwiftyStateMachine-Mac.xcscheme @@ -1,6 +1,6 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -66,11 +66,11 @@ + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -66,11 +66,11 @@