diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bac9fa7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Mac OS X +*.DS_Store + + +# Xcode +*.pbxuser +*.mode2v3 +*.mode1v3 +*.perspective +*.perspectivev3 +*.xcuserstate +xcuserdata/ + +## Generic Files To Ignore +*~ +*.swp +*.out + +# Carthage +Carthage/Checkouts +*.bcsymbolmap + +AppStoreBuild + +.vscode \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..9725cc8 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,12 @@ +language: objective-c + +branches: + only: + - master + +xcode_project: Stately.xcodeproj +xcode_scheme: macOSStately +osx_image: xcode8.1 + +script: +- xcodebuild clean build test -project Stately.xcodeproj -scheme macOSStately diff --git a/Documentation/AddFramework.png b/Documentation/AddFramework.png new file mode 100644 index 0000000..a4b27ea Binary files /dev/null and b/Documentation/AddFramework.png differ diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..9b67b7a --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,9 @@ +The MIT License (MIT) + +Copyright (c) 2016 Softwarenerd. + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Logo/Header.png b/Logo/Header.png new file mode 100644 index 0000000..cec6c2d Binary files /dev/null and b/Logo/Header.png differ diff --git a/README.md b/README.md new file mode 100644 index 0000000..6c2d86b --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +![](Logo/Header.png) + +# Stately [![GitHub license](https://img.shields.io/badge/license-MIT-lightgrey.svg)](https://raw.githubusercontent.com/softwarenerd/Stately/master/LICENSE.md) [![GitHub release](https://img.shields.io/github/release/softwarenerd/Stately.svg)](https://github.com/softwarenerd/Stately/releases) [![Build Status](https://travis-ci.org/softwarenerd/Stately.svg?branch=master)](https://travis-ci.org/softwarenerd/Stately) + +## Introduction + +[Stately](https://github.com/softwarenerd/Stately) is a pure Swift framework for iOS, macOS, watchOS, and tvOS that implements an event-driven finite-state machine. + +For information on finite-state machines, see the Wikipedia article [Finite-state machine](https://en.wikipedia.org/wiki/Finite-state_machine). + +## Background + +I have used variations of Stately in numerous iOS, macOS and .NET applications, and I've found it to be an invaluable tool. I created this open-source version of it for iOS, macOS, watchOS, and tvOS so that other people can both benefit from it and contribute to it. + +### Objective-C Compatibility + +At this time, Stately cannot be used with Objective-C projects. I decided that it makes the most sense to share Stately as a pure Swift framework. If it becomes clear that Objective-C compatibility is needed, I will add it. + +## Quick Links + +- [Getting Started](#getting-started) +- [Basic Documentation](#basic-documentation) +- [Example Project](#example-project) +- [Contributing](#contributing) +- [License](#license) + +## Getting Started + +Stately can be used via [Carthage](https://github.com/Carthage/Carthage). + +There are excellent [Insructions](https://github.com/Carthage/Carthage#getting-started) available on the [Carthage](https://github.com/Carthage/Carthage) site, which are summarized below. + +#### Add Stately to your Cartfile + +``` +github "softwarenerd/Stately" +``` + +#### Update Carthage + +```sh +carthage update +``` + +This will fetch dependencies into a `Carthage/Checkouts` folder, then build each one. + +#### Using Stately - macOS + +On your application targets’ “General” settings tab, in the “Embedded Binaries” section, drag and drop the Stately.framework file from the [Carthage/Build/Mac][] folder on disk. + +Additionally, you'll need to copy debug symbols for debugging and crash reporting on OS X. + +#### Using Stately - iOS, tvOS, watchOS + +1. On your application targets’ “General” settings tab, in the “Linked Frameworks and Libraries” section, drag and drop Stately.framework you want to use from the appropriate [Carthage/Build][] folder on disk. +1. On your application targets’ “Build Phases” settings tab, click the “+” icon and choose “New Run Script Phase”. Create a Run Script in which you specify your shell (ex: `bin/sh`), add the following contents to the script area below the shell: + + ```sh + /usr/local/bin/carthage copy-frameworks + ``` + + and add the paths to the frameworks you want to use under “Input Files”, e.g.: + + ``` + $(SRCROOT)/Carthage/Build/iOS/Stately.framework + ``` + + This script works around an [App Store submission bug](http://www.openradar.me/radar?id=6409498411401216) triggered by universal binaries and ensures that necessary bitcode-related files and dSYMs are copied when archiving. + +With the debug information copied into the built products directory, Xcode will be able to symbolicate the stack trace whenever you stop at a breakpoint. + +When archiving your application for submission to the App Store or TestFlight, Xcode will also copy these files into the dSYMs subdirectory of your application’s `.xcarchive` bundle. + +## Basic Documentation + +Using [Stately](https://github.com/softwarenerd/Stately) is easy and straightforward. + +```swift +var stateClosed: State! +var stateOpened: State! +var eventOpen: Event! +var eventClose: Event! +var stateMachine: StateMachine! + +do { + // Define the states that the state machine can be in. + stateClosed = try State(name: "Closed") { (object: AnyObject?) -> StateChange? in + // Log. + print("Closed") + + // Return, leaving state unchanged. + return nil + } + stateOpened = try State(name: "Opened") { (object: AnyObject?) -> StateChange? in + // Log. + print("Opened") + + // Return, leaving state unchanged. + return nil + } + + // Define the events that can be sent to the state machine. + eventOpen = try Event(name: "Open", transitions: [(fromState: stateClosed, toState: stateOpened)]) + eventClose = try Event(name: "Close", transitions: [(fromState: stateOpened, toState: stateClosed)]) + + // Initialize the state machine. + stateMachine = try StateMachine(name: "Door", + defaultState: stateClosed, + states: [stateClosed, stateOpened], + events: [eventClose, eventOpen]) + + // Fire events to the state machine. + stateMachine.fireEvent(event: eventOpen) + stateMachine.fireEvent(event: eventClose) +} catch { + // Handle errors. +} +``` + +## Example Project + +The [StatelyExample](https://github.com/softwarenerd/StatelyExample) project provides a comprehensive example of using Stately to build a garage door simulator. + +## Contributing + +Stately is a work in progress and your contributions are most welcome. Feel free to fork the repo and submit PR's. + +## License + +Stately is released under the [MIT License](LICENSE.md). diff --git a/Stately.xcodeproj/project.pbxproj b/Stately.xcodeproj/project.pbxproj new file mode 100644 index 0000000..59d6bbf --- /dev/null +++ b/Stately.xcodeproj/project.pbxproj @@ -0,0 +1,990 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + A20770031DB006EE00955B1C /* Stately.h in Headers */ = {isa = PBXBuildFile; fileRef = D36E950F1DAC6DC300B36720 /* Stately.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A20770041DB006F900955B1C /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474341DAC2343000E52D2 /* Event.swift */; }; + A20770051DB006F900955B1C /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474351DAC2343000E52D2 /* State.swift */; }; + A20770061DB006F900955B1C /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474361DAC2343000E52D2 /* StateMachine.swift */; }; + A20770101DB0078100955B1C /* Stately.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2076FFB1DB006E000955B1C /* Stately.framework */; }; + A20770161DB007A200955B1C /* StatelyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F3987B1DA3F3290094B363 /* StatelyTests.swift */; }; + A2259AAF1DB02C250062AA43 /* Stately.h in Headers */ = {isa = PBXBuildFile; fileRef = D36E950F1DAC6DC300B36720 /* Stately.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A2259AB01DB02C2D0062AA43 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474341DAC2343000E52D2 /* Event.swift */; }; + A2259AB11DB02C2D0062AA43 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474351DAC2343000E52D2 /* State.swift */; }; + A2259AB21DB02C2D0062AA43 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474361DAC2343000E52D2 /* StateMachine.swift */; }; + A2617C231DB02A960093484B /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474341DAC2343000E52D2 /* Event.swift */; }; + A2617C241DB02A960093484B /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474351DAC2343000E52D2 /* State.swift */; }; + A2617C251DB02A960093484B /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474361DAC2343000E52D2 /* StateMachine.swift */; }; + A2617C261DB02A9C0093484B /* Stately.h in Headers */ = {isa = PBXBuildFile; fileRef = D36E950F1DAC6DC300B36720 /* Stately.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A270D3271DBE4BBA004D1A43 /* Stately.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2259AA71DB02BE80062AA43 /* Stately.framework */; }; + A270D32D1DBE4C58004D1A43 /* StatelyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F3987B1DA3F3290094B363 /* StatelyTests.swift */; }; + A2838A181DB0092F00BEE5C2 /* Stately.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A2C614131DB0055200C347B2 /* Stately.framework */; }; + A2838A1E1DB0099F00BEE5C2 /* StatelyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = A2F3987B1DA3F3290094B363 /* StatelyTests.swift */; }; + A2C6141B1DB0057D00C347B2 /* Stately.h in Headers */ = {isa = PBXBuildFile; fileRef = D36E950F1DAC6DC300B36720 /* Stately.h */; settings = {ATTRIBUTES = (Public, ); }; }; + A2C6141C1DB0058500C347B2 /* Event.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474341DAC2343000E52D2 /* Event.swift */; }; + A2C6141D1DB0058500C347B2 /* State.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474351DAC2343000E52D2 /* State.swift */; }; + A2C6141E1DB0058500C347B2 /* StateMachine.swift in Sources */ = {isa = PBXBuildFile; fileRef = A21474361DAC2343000E52D2 /* StateMachine.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + A20770111DB0078100955B1C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A2F398641DA3F3280094B363 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2076FFA1DB006E000955B1C; + remoteInfo = macOSStately; + }; + A270D3281DBE4BBA004D1A43 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A2F398641DA3F3280094B363 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2259AA61DB02BE80062AA43; + remoteInfo = tvOSStately; + }; + A2838A191DB0092F00BEE5C2 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = A2F398641DA3F3280094B363 /* Project object */; + proxyType = 1; + remoteGlobalIDString = A2C614121DB0055200C347B2; + remoteInfo = iOSStately; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + A2076FFB1DB006E000955B1C /* Stately.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stately.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A207700B1DB0078100955B1C /* macOSStatelyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = macOSStatelyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A21474341DAC2343000E52D2 /* Event.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Event.swift; path = Code/Event.swift; sourceTree = ""; }; + A21474351DAC2343000E52D2 /* State.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = State.swift; path = Code/State.swift; sourceTree = ""; }; + A21474361DAC2343000E52D2 /* StateMachine.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = StateMachine.swift; path = Code/StateMachine.swift; sourceTree = ""; }; + A2259AA71DB02BE80062AA43 /* Stately.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stately.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A2617C1B1DB02A800093484B /* Stately.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stately.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A270D3221DBE4BBA004D1A43 /* tvOSStatelyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = tvOSStatelyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A2838A131DB0092F00BEE5C2 /* iOSStatelyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iOSStatelyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + A2C614131DB0055200C347B2 /* Stately.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Stately.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + A2F398711DA3F3280094B363 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + A2F3987B1DA3F3290094B363 /* StatelyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatelyTests.swift; sourceTree = ""; }; + A2F3987D1DA3F3290094B363 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D36E950F1DAC6DC300B36720 /* Stately.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Stately.h; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + A2076FF71DB006E000955B1C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A20770081DB0078100955B1C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A20770101DB0078100955B1C /* Stately.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2259AA31DB02BE80062AA43 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2617C171DB02A800093484B /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A270D31F1DBE4BBA004D1A43 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A270D3271DBE4BBA004D1A43 /* Stately.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2838A101DB0092F00BEE5C2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + A2838A181DB0092F00BEE5C2 /* Stately.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2C6140F1DB0055200C347B2 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + A21474331DAC231D000E52D2 /* Code */ = { + isa = PBXGroup; + children = ( + A21474351DAC2343000E52D2 /* State.swift */, + A21474341DAC2343000E52D2 /* Event.swift */, + A21474361DAC2343000E52D2 /* StateMachine.swift */, + ); + name = Code; + sourceTree = ""; + }; + A2F398631DA3F3280094B363 = { + isa = PBXGroup; + children = ( + A2F3986F1DA3F3280094B363 /* Stately */, + A2F3987A1DA3F3290094B363 /* StatelyTests */, + A2F3986E1DA3F3280094B363 /* Products */, + ); + sourceTree = ""; + }; + A2F3986E1DA3F3280094B363 /* Products */ = { + isa = PBXGroup; + children = ( + A2C614131DB0055200C347B2 /* Stately.framework */, + A2076FFB1DB006E000955B1C /* Stately.framework */, + A207700B1DB0078100955B1C /* macOSStatelyTests.xctest */, + A2838A131DB0092F00BEE5C2 /* iOSStatelyTests.xctest */, + A2617C1B1DB02A800093484B /* Stately.framework */, + A2259AA71DB02BE80062AA43 /* Stately.framework */, + A270D3221DBE4BBA004D1A43 /* tvOSStatelyTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + A2F3986F1DA3F3280094B363 /* Stately */ = { + isa = PBXGroup; + children = ( + A21474331DAC231D000E52D2 /* Code */, + A2F398711DA3F3280094B363 /* Info.plist */, + D36E950F1DAC6DC300B36720 /* Stately.h */, + ); + path = Stately; + sourceTree = ""; + }; + A2F3987A1DA3F3290094B363 /* StatelyTests */ = { + isa = PBXGroup; + children = ( + A2F3987B1DA3F3290094B363 /* StatelyTests.swift */, + A2F3987D1DA3F3290094B363 /* Info.plist */, + ); + path = StatelyTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + A2076FF81DB006E000955B1C /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A20770031DB006EE00955B1C /* Stately.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2259AA41DB02BE80062AA43 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A2259AAF1DB02C250062AA43 /* Stately.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2617C181DB02A800093484B /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A2617C261DB02A9C0093484B /* Stately.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2C614101DB0055200C347B2 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + A2C6141B1DB0057D00C347B2 /* Stately.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + A2076FFA1DB006E000955B1C /* macOSStately */ = { + isa = PBXNativeTarget; + buildConfigurationList = A20770021DB006E000955B1C /* Build configuration list for PBXNativeTarget "macOSStately" */; + buildPhases = ( + A2076FF61DB006E000955B1C /* Sources */, + A2076FF71DB006E000955B1C /* Frameworks */, + A2076FF81DB006E000955B1C /* Headers */, + A2076FF91DB006E000955B1C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = macOSStately; + productName = macOSStately; + productReference = A2076FFB1DB006E000955B1C /* Stately.framework */; + productType = "com.apple.product-type.framework"; + }; + A207700A1DB0078100955B1C /* macOSStatelyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A20770131DB0078100955B1C /* Build configuration list for PBXNativeTarget "macOSStatelyTests" */; + buildPhases = ( + A20770071DB0078100955B1C /* Sources */, + A20770081DB0078100955B1C /* Frameworks */, + A20770091DB0078100955B1C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A20770121DB0078100955B1C /* PBXTargetDependency */, + ); + name = macOSStatelyTests; + productName = macOSStatelyTests; + productReference = A207700B1DB0078100955B1C /* macOSStatelyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A2259AA61DB02BE80062AA43 /* tvOSStately */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2259AAC1DB02BE80062AA43 /* Build configuration list for PBXNativeTarget "tvOSStately" */; + buildPhases = ( + A2259AA21DB02BE80062AA43 /* Sources */, + A2259AA31DB02BE80062AA43 /* Frameworks */, + A2259AA41DB02BE80062AA43 /* Headers */, + A2259AA51DB02BE80062AA43 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = tvOSStately; + productName = tvOSStately; + productReference = A2259AA71DB02BE80062AA43 /* Stately.framework */; + productType = "com.apple.product-type.framework"; + }; + A2617C1A1DB02A800093484B /* watchOSStately */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2617C201DB02A800093484B /* Build configuration list for PBXNativeTarget "watchOSStately" */; + buildPhases = ( + A2617C161DB02A800093484B /* Sources */, + A2617C171DB02A800093484B /* Frameworks */, + A2617C181DB02A800093484B /* Headers */, + A2617C191DB02A800093484B /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = watchOSStately; + productName = watchOSStately; + productReference = A2617C1B1DB02A800093484B /* Stately.framework */; + productType = "com.apple.product-type.framework"; + }; + A270D3211DBE4BBA004D1A43 /* tvOSStatelyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A270D32A1DBE4BBA004D1A43 /* Build configuration list for PBXNativeTarget "tvOSStatelyTests" */; + buildPhases = ( + A270D31E1DBE4BBA004D1A43 /* Sources */, + A270D31F1DBE4BBA004D1A43 /* Frameworks */, + A270D3201DBE4BBA004D1A43 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A270D3291DBE4BBA004D1A43 /* PBXTargetDependency */, + ); + name = tvOSStatelyTests; + productName = tvOSStatelyTests; + productReference = A270D3221DBE4BBA004D1A43 /* tvOSStatelyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A2838A121DB0092F00BEE5C2 /* iOSStatelyTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2838A1B1DB0092F00BEE5C2 /* Build configuration list for PBXNativeTarget "iOSStatelyTests" */; + buildPhases = ( + A2838A0F1DB0092F00BEE5C2 /* Sources */, + A2838A101DB0092F00BEE5C2 /* Frameworks */, + A2838A111DB0092F00BEE5C2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + A2838A1A1DB0092F00BEE5C2 /* PBXTargetDependency */, + ); + name = iOSStatelyTests; + productName = iOSStatelyTests; + productReference = A2838A131DB0092F00BEE5C2 /* iOSStatelyTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; + A2C614121DB0055200C347B2 /* iOSStately */ = { + isa = PBXNativeTarget; + buildConfigurationList = A2C614181DB0055200C347B2 /* Build configuration list for PBXNativeTarget "iOSStately" */; + buildPhases = ( + A2C6140E1DB0055200C347B2 /* Sources */, + A2C6140F1DB0055200C347B2 /* Frameworks */, + A2C614101DB0055200C347B2 /* Headers */, + A2C614111DB0055200C347B2 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = iOSStately; + productName = iOSStately; + productReference = A2C614131DB0055200C347B2 /* Stately.framework */; + productType = "com.apple.product-type.framework"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + A2F398641DA3F3280094B363 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0800; + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = Softwarenerd; + TargetAttributes = { + A2076FFA1DB006E000955B1C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A207700A1DB0078100955B1C = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A2259AA61DB02BE80062AA43 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A2617C1A1DB02A800093484B = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A270D3211DBE4BBA004D1A43 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A2838A121DB0092F00BEE5C2 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + A2C614121DB0055200C347B2 = { + CreatedOnToolsVersion = 8.0; + DevelopmentTeam = 6B5V5J8CTY; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = A2F398671DA3F3280094B363 /* Build configuration list for PBXProject "Stately" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = A2F398631DA3F3280094B363; + productRefGroup = A2F3986E1DA3F3280094B363 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + A2C614121DB0055200C347B2 /* iOSStately */, + A2076FFA1DB006E000955B1C /* macOSStately */, + A2259AA61DB02BE80062AA43 /* tvOSStately */, + A2617C1A1DB02A800093484B /* watchOSStately */, + A2838A121DB0092F00BEE5C2 /* iOSStatelyTests */, + A207700A1DB0078100955B1C /* macOSStatelyTests */, + A270D3211DBE4BBA004D1A43 /* tvOSStatelyTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + A2076FF91DB006E000955B1C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A20770091DB0078100955B1C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2259AA51DB02BE80062AA43 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2617C191DB02A800093484B /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A270D3201DBE4BBA004D1A43 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2838A111DB0092F00BEE5C2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2C614111DB0055200C347B2 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + A2076FF61DB006E000955B1C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A20770051DB006F900955B1C /* State.swift in Sources */, + A20770041DB006F900955B1C /* Event.swift in Sources */, + A20770061DB006F900955B1C /* StateMachine.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A20770071DB0078100955B1C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A20770161DB007A200955B1C /* StatelyTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2259AA21DB02BE80062AA43 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2259AB11DB02C2D0062AA43 /* State.swift in Sources */, + A2259AB01DB02C2D0062AA43 /* Event.swift in Sources */, + A2259AB21DB02C2D0062AA43 /* StateMachine.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2617C161DB02A800093484B /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2617C241DB02A960093484B /* State.swift in Sources */, + A2617C231DB02A960093484B /* Event.swift in Sources */, + A2617C251DB02A960093484B /* StateMachine.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A270D31E1DBE4BBA004D1A43 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A270D32D1DBE4C58004D1A43 /* StatelyTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2838A0F1DB0092F00BEE5C2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2838A1E1DB0099F00BEE5C2 /* StatelyTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + A2C6140E1DB0055200C347B2 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + A2C6141D1DB0058500C347B2 /* State.swift in Sources */, + A2C6141C1DB0058500C347B2 /* Event.swift in Sources */, + A2C6141E1DB0058500C347B2 /* StateMachine.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + A20770121DB0078100955B1C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A2076FFA1DB006E000955B1C /* macOSStately */; + targetProxy = A20770111DB0078100955B1C /* PBXContainerItemProxy */; + }; + A270D3291DBE4BBA004D1A43 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A2259AA61DB02BE80062AA43 /* tvOSStately */; + targetProxy = A270D3281DBE4BBA004D1A43 /* PBXContainerItemProxy */; + }; + A2838A1A1DB0092F00BEE5C2 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = A2C614121DB0055200C347B2 /* iOSStately */; + targetProxy = A2838A191DB0092F00BEE5C2 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + A20770001DB006E000955B1C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.macOSStately; + PRODUCT_NAME = Stately; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + A20770011DB006E000955B1C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + FRAMEWORK_VERSION = A; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.macOSStately; + PRODUCT_NAME = Stately; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + A20770141DB0078100955B1C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.macOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + A20770151DB0078100955B1C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/../Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.macOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + A2259AAD1DB02BE80062AA43 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.tvOSStately; + PRODUCT_NAME = Stately; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + A2259AAE1DB02BE80062AA43 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.tvOSStately; + PRODUCT_NAME = Stately; + SDKROOT = appletvos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 3; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + A2617C211DB02A800093484B /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.watchOSStately; + PRODUCT_NAME = Stately; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 3.0; + }; + name = Debug; + }; + A2617C221DB02A800093484B /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.watchOSStately; + PRODUCT_NAME = Stately; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 3.0; + }; + name = Release; + }; + A270D32B1DBE4BBA004D1A43 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.tvOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Debug; + }; + A270D32C1DBE4BBA004D1A43 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.tvOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = appletvos; + SWIFT_VERSION = 3.0; + TVOS_DEPLOYMENT_TARGET = 10.0; + }; + name = Release; + }; + A2838A1C1DB0092F00BEE5C2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.iOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + A2838A1D1DB0092F00BEE5C2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEVELOPMENT_TEAM = 6B5V5J8CTY; + INFOPLIST_FILE = StatelyTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.iOSStatelyTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + A2C614191DB0055200C347B2 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.iOSStately; + PRODUCT_NAME = Stately; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Debug; + }; + A2C6141A1DB0055200C347B2 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = 6B5V5J8CTY; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = "$(SRCROOT)/Stately/Info.plist"; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.softwarenerd.iOSStately; + PRODUCT_NAME = Stately; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + }; + name = Release; + }; + A2F3987F1DA3F3290094B363 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + A2F398801DA3F3290094B363 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + 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_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + A20770021DB006E000955B1C /* Build configuration list for PBXNativeTarget "macOSStately" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A20770001DB006E000955B1C /* Debug */, + A20770011DB006E000955B1C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A20770131DB0078100955B1C /* Build configuration list for PBXNativeTarget "macOSStatelyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A20770141DB0078100955B1C /* Debug */, + A20770151DB0078100955B1C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2259AAC1DB02BE80062AA43 /* Build configuration list for PBXNativeTarget "tvOSStately" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2259AAD1DB02BE80062AA43 /* Debug */, + A2259AAE1DB02BE80062AA43 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2617C201DB02A800093484B /* Build configuration list for PBXNativeTarget "watchOSStately" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2617C211DB02A800093484B /* Debug */, + A2617C221DB02A800093484B /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A270D32A1DBE4BBA004D1A43 /* Build configuration list for PBXNativeTarget "tvOSStatelyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A270D32B1DBE4BBA004D1A43 /* Debug */, + A270D32C1DBE4BBA004D1A43 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2838A1B1DB0092F00BEE5C2 /* Build configuration list for PBXNativeTarget "iOSStatelyTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2838A1C1DB0092F00BEE5C2 /* Debug */, + A2838A1D1DB0092F00BEE5C2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2C614181DB0055200C347B2 /* Build configuration list for PBXNativeTarget "iOSStately" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2C614191DB0055200C347B2 /* Debug */, + A2C6141A1DB0055200C347B2 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + A2F398671DA3F3280094B363 /* Build configuration list for PBXProject "Stately" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + A2F3987F1DA3F3290094B363 /* Debug */, + A2F398801DA3F3290094B363 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = A2F398641DA3F3280094B363 /* Project object */; +} diff --git a/Stately.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Stately.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000..e81709c --- /dev/null +++ b/Stately.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/iOSStately.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/iOSStately.xcscheme new file mode 100644 index 0000000..aee6119 --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/iOSStately.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/iOSStatelyTests.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/iOSStatelyTests.xcscheme new file mode 100644 index 0000000..6dc86a7 --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/iOSStatelyTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/macOSStately.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/macOSStately.xcscheme new file mode 100644 index 0000000..31ec7bf --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/macOSStately.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/macOSStatelyTests.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/macOSStatelyTests.xcscheme new file mode 100644 index 0000000..0b050f2 --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/macOSStatelyTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStately.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStately.xcscheme new file mode 100644 index 0000000..00ab35f --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStately.xcscheme @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStatelyTests.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStatelyTests.xcscheme new file mode 100644 index 0000000..24faf7b --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/tvOSStatelyTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately.xcodeproj/xcshareddata/xcschemes/watchOSStately.xcscheme b/Stately.xcodeproj/xcshareddata/xcschemes/watchOSStately.xcscheme new file mode 100644 index 0000000..d943b5c --- /dev/null +++ b/Stately.xcodeproj/xcshareddata/xcschemes/watchOSStately.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Stately/Code/Event.swift b/Stately/Code/Event.swift new file mode 100644 index 0000000..ba13500 --- /dev/null +++ b/Stately/Code/Event.swift @@ -0,0 +1,97 @@ +// +// Event.swift +// Stately +// +// Created by Brian Lambert on 10/4/16. +// See the LICENSE.md file in the project root for license information. +// + +import Foundation + +// Types alias for a transition tuple. +public typealias Transition = (fromState: State, toState: State) + +// Event error enumeration. +public enum EventError: Error { + case NameEmpty + case NoTransitions + case DuplicateTransition(fromState: State) +} + +// Event class. +public class Event : Hashable { + // The name of the event. + public let name: String + + // The set of transitions for the event. + let transitions: [Transition] + + /// Initializes a new instance of the Event class. + /// + /// - Parameters: + /// - name: The name of the event. Each event must have a unique name. + /// - transitions: The the event transitions. Each event must define at least one transition. + public init(name nameIn: String, transitions transitionsIn: [Transition]) throws { + // Validate the event name. + if nameIn.isEmpty { + throw EventError.NameEmpty + } + + // At least one transition must be specified. + if transitionsIn.count == 0 { + throw EventError.NoTransitions + } + + // Ensure that there are no duplicate from state transitions defined. While this wouldn't strictly + // be a bad thing, the presence of duplicate from state transitions more than likely indicates that + // there is a bug in the definition of the state machine, so we don't allow it. + var fromStatesTemp = Set(minimumCapacity: transitionsIn.count) + for transition in transitionsIn { + if fromStatesTemp.contains(transition.fromState) { + throw EventError.DuplicateTransition(fromState: transition.fromState) + } else { + fromStatesTemp.insert(transition.fromState) + } + } + + // Initialize. + name = nameIn + transitions = transitionsIn + } + + /// Returns the transition with the specified from state, if one is found; otherwise, nil. + /// + /// - Parameters: + /// - fromState: The from state. + func transition(fromState: State) -> State? { + // Find the transition. If it cannot be found, return nil. + guard let transition = (transitions.first(where: { (transition: Transition) -> Bool in return transition.fromState === fromState })) else { + return nil; + } + + // Return the to state. + return transition.toState; + } + + /// Gets the hash value. + /// + /// Hash values are not guaranteed to be equal across different executions of + /// your program. Do not save hash values to use during a future execution. + public var hashValue: Int { + get { + return name.hashValue + } + } + + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Equality is the inverse of inequality. For any values `a` and `b`, + /// `a == b` implies that `a != b` is `false`. + /// + /// - Parameters: + /// - lhs: A value to compare. + /// - rhs: Another value to compare. + public static func ==(lhs: Event, rhs: Event) -> Bool { + return lhs === rhs + } +} diff --git a/Stately/Code/State.swift b/Stately/Code/State.swift new file mode 100644 index 0000000..205dc52 --- /dev/null +++ b/Stately/Code/State.swift @@ -0,0 +1,84 @@ +// +// State.swift +// Stately +// +// Created by Brian Lambert on 10/4/16. +// See the LICENSE.md file in the project root for license information. +// + +import Foundation + +// State error enumeration. +public enum StateError: Error { + case NameEmpty +} + +// Types alias for a state change tuple. +public typealias StateChange = (state: State, object: AnyObject?) + +// Type alias for a state enter action. +public typealias StateEnterAction = (AnyObject?) throws -> StateChange? + +// State class. +public class State: Hashable { + // Gets the name of the state. + public let name: String + + // The optional state enter action. + internal let stateEnterAction: StateEnterAction? + + /// Initializes a new instance of the State class. + /// + /// - Parameters: + /// - name: The name of the state. Each state must have a unique name. + public convenience init(name nameIn: String) throws { + try self.init(name: nameIn, stateEnterAction: nil) + } + + /// Initializes a new instance of the State class. + /// + /// - Parameters: + /// - name: The name of the state. Each state must have a unique name. + /// - stateEnterAction: The state enter action. + public convenience init(name nameIn: String, stateEnterAction stateEnterActionIn: @escaping StateEnterAction) throws { + try self.init(name: nameIn, stateEnterAction: StateEnterAction?(stateEnterActionIn)) + } + + /// Initializes a new instance of the State class. + /// + /// - Parameters: + /// - name: The name of the state. Each state must have a unique name. + /// - stateAction: The optional state enter action. + private init(name nameIn: String, stateEnterAction stateEnterActionIn: StateEnterAction?) throws { + // Validate the state name. + if nameIn.isEmpty { + throw StateError.NameEmpty + } + + // Initialize. + name = nameIn + stateEnterAction = stateEnterActionIn + } + + /// Gets the hash value. + /// + /// Hash values are not guaranteed to be equal across different executions of + /// your program. Do not save hash values to use during a future execution. + public var hashValue: Int { + get { + return name.hashValue + } + } + + /// Returns a Boolean value indicating whether two values are equal. + /// + /// Equality is the inverse of inequality. For any values `a` and `b`, + /// `a == b` implies that `a != b` is `false`. + /// + /// - Parameters: + /// - lhs: A value to compare. + /// - rhs: Another value to compare. + public static func ==(lhs: State, rhs: State) -> Bool { + return lhs === rhs + } +} diff --git a/Stately/Code/StateMachine.swift b/Stately/Code/StateMachine.swift new file mode 100644 index 0000000..58ebae8 --- /dev/null +++ b/Stately/Code/StateMachine.swift @@ -0,0 +1,178 @@ +// +// StateMachine.swift +// Stately +// +// Created by Brian Lambert on 10/4/16. +// See the LICENSE.md file in the project root for license information. +// + +import Foundation + +// State machine error enumeration. +public enum StateMachineError: Error { + case NoStatesDefined + case DefaultStateUndefined + case DuplicateStateDefinition(state: State) + case DuplicateStateNameDefinition(name: String) + case NoEventsDefined + case DuplicateEventDefinition(event: Event) + case DuplicateEventNameDefinition(name: String) + case TransitionFromStateNotDefined(fromState: State) + case TransitionToStateNotDefined(toState: State) + case UndefiendEventFired + case UndefiendState(state: State) + case NoTransitionFromCurrentStateFound(currentState: State) +} + +// StateMachine class. +public class StateMachine { + // The name of the state machine. + public let name: String + + // The states that are defined in the state machine. + private let states: Set + + // The events that are defined in the state machine. + private let events: Set + + // The serial dispatch queue used to synchronize access to the state machine. + private let serialDispatchQueue: DispatchQueue + + // The state. + private var state: State + + /// Initializes a new instance of the StateMachine class. + /// + /// - Parameters: + /// - name: The name of the state machine. + /// - defaultState: The default state. This state must be one of the states supplied in the states array. + /// - states: An array of states that the state machine can be in. + /// - events: An array of events that can be fired on the state machine. + public init(name nameIn: String, defaultState: State, states statesIn: [State], events eventsIn: [Event]) throws { + // Ensure that at least one state is defined. + if statesIn.count == 0 { + throw StateMachineError.NoStatesDefined + } + + // Ensure that at least one event is defined. + if eventsIn.count == 0 { + throw StateMachineError.NoEventsDefined + } + + // Construct the set of states. + var statesNameDictionaryTemp = Dictionary(minimumCapacity: statesIn.count) + var statesTemp = Set(minimumCapacity: statesIn.count) + for state in statesIn { + // Ensure that this isn't a duplicate state. + if statesTemp.contains(state) { + throw StateMachineError.DuplicateStateDefinition(state: state) + } else if statesNameDictionaryTemp[state.name] != nil { + throw StateMachineError.DuplicateStateNameDefinition(name: state.name) + } else { + // Insert the state. + statesNameDictionaryTemp[state.name] = state + statesTemp.insert(state) + } + } + + // Construct the set of events. + var eventsNameDictionaryTemp = Dictionary(minimumCapacity: statesIn.count) + var eventsTemp = Set(minimumCapacity: eventsIn.count) + for event in eventsIn { + // Ensure that this isn't a duplicate event. + if eventsTemp.contains(event) { + throw StateMachineError.DuplicateEventDefinition(event: event) + } else if eventsNameDictionaryTemp[event.name] != nil { + throw StateMachineError.DuplicateEventNameDefinition(name: event.name) + } else { + // Validate the transitions for the event. + for transition in event.transitions { + // Ensure that the from state is defined. + if !statesTemp.contains(transition.fromState) { + throw StateMachineError.TransitionFromStateNotDefined(fromState: transition.fromState) + } + + // Ensure that the to state is defined. + if !statesTemp.contains(transition.toState) { + throw StateMachineError.TransitionToStateNotDefined(toState: transition.toState) + } + } + + // Insert the event. + eventsNameDictionaryTemp[event.name] = event; + eventsTemp.insert(event) + } + } + + // Ensure that the default state is defined in the set of states. + if !statesTemp.contains(defaultState) { + throw StateMachineError.DefaultStateUndefined + } + + // All checks have been performed and the state machine appears to be valid. Initialize members. + serialDispatchQueue = DispatchQueue(label: "StatelyStateMachineTimeout:\(nameIn)") + name = nameIn + states = statesTemp + events = eventsTemp + state = try State(name: "[None]") + try changeState(stateChange: StateChange(defaultState, nil)) + } + + /// Fires an event. + /// + /// - Parameters: + /// - event: The event to fire. + public func fireEvent(event: Event) throws { + try fireEvent(event: event, object: nil) + } + + /// Fires an event. + /// + /// - Parameters: + /// - event: The event to fire. + /// - object: An optional object which represents additional information for the event. + public func fireEvent(event: Event, object: AnyObject?) throws { + // Ensure that the event is defined in the state machine. + guard events.contains(event) else { + throw StateMachineError.UndefiendEventFired + } + + // Fire the event. + try serialDispatchQueue.sync { + // Obtain the state to transition to. + guard let toState = event.transition(fromState: state) else { + throw StateMachineError.NoTransitionFromCurrentStateFound(currentState: state) + } + + // Change state. + try changeState(stateChange: StateChange(toState, object)) + } + } + + /// Changes state. + /// + /// - Parameters: + /// - stateChange: A tuple representing the state change. + private func changeState(stateChange stateChangeIn: StateChange) throws { + // Perform the state change, if it's changing. + var stateChange = stateChangeIn + while stateChange.state != state { + // Set the new state. + state = stateChange.state + + // If there's no state enter action for the new state, we're done. If there is a state enter action, perform it and, + // if it doesn't result in an immediate state change, we're done. + guard let stateEnterAction = state.stateEnterAction, let immediateStateChange = try stateEnterAction(stateChange.object) else { + break + } + + // Check that the immediate state change's state is defined in the state machine. + guard states.contains(immediateStateChange.state) else { + throw StateMachineError.UndefiendState(state: immediateStateChange.state) + } + + // Set-up the immediate state change for the next loop iteration. + stateChange = immediateStateChange + } + } +} diff --git a/Stately/Info.plist b/Stately/Info.plist new file mode 100644 index 0000000..fbe1e6b --- /dev/null +++ b/Stately/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/Stately/Stately.h b/Stately/Stately.h new file mode 100644 index 0000000..84eb5ba --- /dev/null +++ b/Stately/Stately.h @@ -0,0 +1,15 @@ +// +// Stately.h +// Stately +// +// Created by Brian Lambert on 10/4/16. +// See the LICENSE.md file in the project root for license information. +// + +#import + +//! Project version number for Stately. +FOUNDATION_EXPORT double StatelyVersionNumber; + +//! Project version string for Stately. +FOUNDATION_EXPORT const unsigned char StatelyVersionString[]; diff --git a/StatelyTests/Info.plist b/StatelyTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/StatelyTests/Info.plist @@ -0,0 +1,22 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleVersion + 1 + + diff --git a/StatelyTests/StatelyTests.swift b/StatelyTests/StatelyTests.swift new file mode 100644 index 0000000..d838b3f --- /dev/null +++ b/StatelyTests/StatelyTests.swift @@ -0,0 +1,264 @@ +// +// StatelyTests.swift +// StatelyTests +// +// Created by Brian Lambert on 10/4/16. +// See the LICENSE.md file in the project root for license information. +// + +import XCTest +@testable import Stately + +// StatelyTests class. +class StatelyTests: XCTestCase +{ + // Sets up the test. This method is called before the invocation of each test method in the class. + override func setUp() { + super.setUp() + } + + // Tears down the test. This method is called after the invocation of each test method in the class. + override func tearDown() { + super.tearDown() + } + + // Test no states defined. + func testNoStatesDefined() { + do { + // Setup. + let stateA = try State(name: "A") + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateA)]) + + // Test. + let _ = try StateMachine(name: "StateMachine", defaultState: stateA, states: [], events: [event1]) + + // Assert. + XCTFail("No error thrown.") + } catch StateMachineError.NoStatesDefined { + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("Wrong error thrown: \(error)") + } + } + + // Test no events defined. + func testNoEventsDefined() { + do { + // Setup. + let stateA = try State(name: "A") + + // Test. + let _ = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA], events: []) + + // Assert. + XCTFail("No error thrown.") + } catch StateMachineError.NoEventsDefined { + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("Wrong error thrown: \(error)") + } + } + + // Tests duplicate state definition. + func testDuplicateStateDefinition() { + do { + // Setup. + let stateA = try State(name: "A") + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateA)]) + + // Test. + let _ = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateA], events: [event1]) + + // Assert. + XCTFail("No error thrown.") + } catch StateMachineError.DuplicateStateDefinition { + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("Wrong error thrown: \(error)") + } + } + + // Tests duplicate state name definition. + func testDuplicateStateNameDefinition() { + do { + // Setup. + let stateA = try State(name: "A") + let stateB = try State(name: "A") + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateB)]) + + // Test. + let _ = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB], events: [event1]) + + // Assert. + XCTFail("No error thrown.") + } catch StateMachineError.DuplicateStateNameDefinition { + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("Wrong error thrown: \(error)") + } + } + + // Tests a valid state transition. + func testValidStateTransition() { + do { + // Setup. + let stateA = try State(name: "A") + let stateB = try State(name: "B") + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateB)]) + let event2 = try Event(name: "2", transitions: [(fromState: stateB, toState: stateA)]) + + // Test. + let stateMachine = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB], events: [event1, event2]) + try stateMachine.fireEvent(event: event1) + + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("An error was thrown: \(error)") + } + } + + // Tests an invalid state transition. + func testInvalidStateTransition() { + do { + // Setup. + let stateA = try State(name: "A") + let stateB = try State(name: "B") + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateB)]) + let event2 = try Event(name: "2", transitions: [(fromState: stateB, toState: stateA)]) + + // Test. + let stateMachine = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB], events: [event1, event2]) + try stateMachine.fireEvent(event: event2) + + // Assert. + XCTFail("No error thrown.") + } catch StateMachineError.NoTransitionFromCurrentStateFound { + // Expect to arrive here. This is success. + } catch { + // Assert. + XCTFail("Wrong error thrown: \(error)") + } + } + + // Tests enter actions. + func testEnterActions() { + do { + // Setup. + var stateAEntered = false + let stateA = try State(name: "A") { (object: AnyObject?) -> StateChange? in + stateAEntered = true + return nil + } + var stateBEntered = false + let stateB = try State(name: "B") { (object: AnyObject?) -> StateChange? in + stateBEntered = true + return nil + } + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateB)]) + let event2 = try Event(name: "2", transitions: [(fromState: stateB, toState: stateA)]) + + // Test. + let stateMachine = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB], events: [event1, event2]) + try stateMachine.fireEvent(event: event1) + try stateMachine.fireEvent(event: event2) + + // Assert. + XCTAssertTrue(stateAEntered) + XCTAssertTrue(stateBEntered) + } + catch { + // Assert. + XCTFail("Unexpeced error thrown: \(error)") + } + } + + // Tests enter actions with an immediate state transition. + func testEnterActionsWithImmediateStateTransition() { + do { + // Setup. + var stateAEntered = false + let stateA = try State(name: "A") { (object: AnyObject?) -> StateChange? in + stateAEntered = true + return nil + } + var stateBEntered = false + let stateB = try State(name: "B") { (object: AnyObject?) -> StateChange? in + stateBEntered = true + return nil + } + var stateCEntered = false + let stateC = try State(name: "C") { (object: AnyObject?) -> StateChange? in + stateCEntered = true + return StateChange(stateB, nil) + } + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateC)]) + let event2 = try Event(name: "2", transitions: [(fromState: stateB, toState: stateA)]) + + // Test. + let stateMachine = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB, stateC], events: [event1, event2]) + try stateMachine.fireEvent(event: event1) + try stateMachine.fireEvent(event: event2) + + // Assert. + XCTAssertTrue(stateAEntered) + XCTAssertTrue(stateBEntered) + XCTAssertTrue(stateCEntered) + } catch { + // Assert. + XCTFail("Unexpeced error thrown: \(error)") + } + } + + // Tests multiple threads. + func testMultipleThreads() { + do { + // Setup. + var stateTransitionCount = 0 + let serialDispatchQueue = DispatchQueue(label: "StatelyTestMultipleThreads") + let stateAction = { (object: AnyObject?) -> StateChange? in + serialDispatchQueue.sync { + stateTransitionCount += 1 + } + + return nil + } + let stateA = try State(name: "A", stateEnterAction: stateAction) + let stateB = try State(name: "B", stateEnterAction: stateAction) + let event1 = try Event(name: "1", transitions: [(fromState: stateA, toState: stateB), (fromState: stateB, toState: stateA)]) + let stateMachine = try StateMachine(name: "StateMachine", defaultState: stateA, states: [stateA, stateB], events: [event1]) + + // Test. + let concurrentDispatchQueue = DispatchQueue(label: "StatelyTestMultipleThreadsTesters", attributes: .concurrent) + var dispatchWorkItems = [DispatchWorkItem]() + for _ in 0...99 { + let dispatchWorkItem = DispatchWorkItem { + for _ in 0...99 { + do { + try stateMachine.fireEvent(event: event1) + } catch { + XCTFail("Unexpeced error thrown: \(error)") + } + } + } + dispatchWorkItems.append(dispatchWorkItem) + concurrentDispatchQueue.asyncAfter(deadline: DispatchTime.now() + 0.2, execute: dispatchWorkItem) + } + + // Wait for testers to be done. + for dispatchWorkItem in dispatchWorkItems { + dispatchWorkItem.wait() + } + + // Assert. + XCTAssertTrue(stateTransitionCount == 10001) + } catch { + // Assert. + XCTFail("Unexpeced error thrown: \(error)") + } + } +}