├── SwiftFSMBannerFinal.png
├── SwiftFSM
├── SwiftFSM
│ ├── Assets.xcassets
│ │ └── AppIcon.appiconset
│ │ │ ├── SwiftFSMAppIcon.png
│ │ │ ├── SwiftFSMAppIcon-1.png
│ │ │ ├── SwiftFSMAppIcon-2.png
│ │ │ ├── SwiftFSMAppIcon180.png
│ │ │ └── Contents.json
│ ├── Info.plist
│ ├── Base.lproj
│ │ ├── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ ├── AppDelegate.swift
│ ├── ViewController.swift
│ └── SwiftFSM.swift
└── SwiftFSM.xcodeproj
│ ├── project.xcworkspace
│ └── contents.xcworkspacedata
│ └── project.pbxproj
├── LICENSE
├── Xcode-config
└── Shared.xcconfig
├── .gitignore
└── README.md
/SwiftFSMBannerFinal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalvshekkar/SwiftFSM/HEAD/SwiftFSMBannerFinal.png
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalvshekkar/SwiftFSM/HEAD/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon.png
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon-1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalvshekkar/SwiftFSM/HEAD/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon-1.png
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon-2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalvshekkar/SwiftFSM/HEAD/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon-2.png
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalvshekkar/SwiftFSM/HEAD/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/SwiftFSMAppIcon180.png
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Vishal V. Shekkar
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Xcode-config/Shared.xcconfig:
--------------------------------------------------------------------------------
1 | #include "DEVELOPMENT_TEAM.xcconfig"
2 |
3 | // Create the file DEVELOPMENT_TEAM.xcconfig
4 | // in the "Xcode-Config" directory within the project directory
5 | // with the following build setting:
6 | // DEVELOPMENT_TEAM = [Your TeamID]
7 |
8 | // Hint: recent Xcode versions appear to automatically create an empty file
9 | // for you on the first build. This build will fail, or course,
10 | // because code-signing can’t work without the DEVELOPMENT_TEAM set.
11 | // Just fill it in and everything should work.
12 |
13 | // You can find your team ID by logging into your Apple Developer account
14 | // and going to
15 | // https://developer.apple.com/account/#/membership
16 | // It should be listed under “Team ID”.
17 |
18 | // To set this system up for your own project,
19 | // copy the "Xcode-Config" directory there,
20 | // add it to your Xcode project,
21 | // navigate to your project settings
22 | // (root icon in the Xcode Project Navigator)
23 | // click on the project icon there,
24 | // click on the “Info” tab
25 | // under “Configurations”
26 | // open the “Debug”, “Release”,
27 | // and any other build configurations you might have.
28 | // There you can set the pull-down menus in the
29 | // “Based on Configuration File” column to “Shared”.
30 | // Done.
31 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | APPL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 | LSRequiresIPhoneOS
22 |
23 | UILaunchStoryboardName
24 | LaunchScreen
25 | UIMainStoryboardFile
26 | Main
27 | UIRequiredDeviceCapabilities
28 |
29 | armv7
30 |
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Xcode
2 | #
3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
4 |
5 | ## Build generated
6 | build/
7 | DerivedData/
8 |
9 | ## Various settings
10 | *.pbxuser
11 | !default.pbxuser
12 | *.mode1v3
13 | !default.mode1v3
14 | *.mode2v3
15 | !default.mode2v3
16 | *.perspectivev3
17 | !default.perspectivev3
18 | xcuserdata/
19 |
20 | ## Other
21 | *.moved-aside
22 | *.xcuserstate
23 |
24 | ## Obj-C/Swift specific
25 | *.hmap
26 | *.ipa
27 | *.dSYM.zip
28 | *.dSYM
29 |
30 | ## Playgrounds
31 | timeline.xctimeline
32 | playground.xcworkspace
33 |
34 | # Swift Package Manager
35 | #
36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
37 | # Packages/
38 | .build/
39 |
40 | # CocoaPods
41 | #
42 | # We recommend against adding the Pods directory to your .gitignore. However
43 | # you should judge for yourself, the pros and cons are mentioned at:
44 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
45 | #
46 | # Pods/
47 |
48 | # Carthage
49 | #
50 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
51 | # Carthage/Checkouts
52 |
53 | Carthage/Build
54 |
55 | # fastlane
56 | #
57 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
58 | # screenshots whenever they are needed.
59 | # For more information about the recommended setup visit:
60 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md
61 |
62 | fastlane/report.xml
63 | fastlane/Preview.html
64 | fastlane/screenshots
65 | fastlane/test_output
66 |
67 | # User-specific xcconfig files
68 | Xcode-config/DEVELOPMENT_TEAM.xcconfig
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "60x60",
35 | "idiom" : "iphone",
36 | "filename" : "SwiftFSMAppIcon-2.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "60x60",
41 | "idiom" : "iphone",
42 | "filename" : "SwiftFSMAppIcon.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "idiom" : "ipad",
47 | "size" : "20x20",
48 | "scale" : "1x"
49 | },
50 | {
51 | "idiom" : "ipad",
52 | "size" : "20x20",
53 | "scale" : "2x"
54 | },
55 | {
56 | "idiom" : "ipad",
57 | "size" : "29x29",
58 | "scale" : "1x"
59 | },
60 | {
61 | "idiom" : "ipad",
62 | "size" : "29x29",
63 | "scale" : "2x"
64 | },
65 | {
66 | "idiom" : "ipad",
67 | "size" : "40x40",
68 | "scale" : "1x"
69 | },
70 | {
71 | "idiom" : "ipad",
72 | "size" : "40x40",
73 | "scale" : "2x"
74 | },
75 | {
76 | "idiom" : "ipad",
77 | "size" : "76x76",
78 | "scale" : "1x"
79 | },
80 | {
81 | "size" : "76x76",
82 | "idiom" : "ipad",
83 | "filename" : "SwiftFSMAppIcon-1.png",
84 | "scale" : "2x"
85 | },
86 | {
87 | "size" : "83.5x83.5",
88 | "idiom" : "ipad",
89 | "filename" : "SwiftFSMAppIcon180.png",
90 | "scale" : "2x"
91 | }
92 | ],
93 | "info" : {
94 | "version" : 1,
95 | "author" : "xcode"
96 | }
97 | }
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // SwiftFSM
4 | //
5 | // Created by Vishal V. Shekkar on 25/10/16.
6 | // Copyright © 2016 Vishal V. Shekkar. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 | var window: UIWindow?
15 |
16 |
17 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
18 | // Override point for customization after application launch.
19 | return true
20 | }
21 |
22 | func applicationWillResignActive(_ application: UIApplication) {
23 | // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
24 | // Use this method to pause ongoing tasks, disable timers, and invalidate graphics rendering callbacks. Games should use this method to pause the game.
25 | }
26 |
27 | func applicationDidEnterBackground(_ application: UIApplication) {
28 | // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
29 | // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
30 | }
31 |
32 | func applicationWillEnterForeground(_ application: UIApplication) {
33 | // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
34 | }
35 |
36 | func applicationDidBecomeActive(_ application: UIApplication) {
37 | // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
38 | }
39 |
40 | func applicationWillTerminate(_ application: UIApplication) {
41 | // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
42 | }
43 |
44 |
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | I needed a finite state machine for a game I was developing in Swift 3. I checked out other FSMs developed in Swift, but, they were either heavy or were not yet ported to Swift 3. Hence, SwiftFSM came about.
4 |
5 | I intended SwiftFSM to be a single-file implementation. I also needed it to scale up to the needs of a complex FSM that my game required. SwiftFSM could achieve all that and still be generalized to support all kinds of states and triggers only because of the power of Swift's protocols and generics.
6 |
7 | # Features
8 |
9 | - **Type Agnostic** - States and Triggers could be represented by any type. Enums, structs (Int, String...) and so on. This gives a lot of leeway to represent states and triggers based on your requirement. All the FSM cares about is if you are able to distinguish between two state or two trigger objects.
10 | - **Closures** - Closures are used instead of delegates to bring about cleaner and a more readable code.
11 | - **Alerts** - Callback for state changes.
12 | - **Allows Exceptions** - Callback before a state change to optionally block a state change.
13 | - **Freedom in Logging** - Logging with custom methods possible.
14 | - **Freedom in Schema Object** - The schema object can be created externally by conforming to a protocol. This lets you add custom functionality to the schema object.
15 |
16 | # Installation
17 |
18 | Download the .zip and extract the files. Open the project in Xcode 8+. Check out the Turnstile example demonstrated in the project by running the project. Drag and Copy the file 'SwiftFSM.swift' to your own project and use it.
19 |
20 | # Build Instructions
21 |
22 | Yes. This is a wall of text, but it’s a pretty simple solution for an annoying problem.
23 |
24 | The example project is configured to code sign the app. For code signing, you need a valid Apple Developer code signing certificate in your keychain, and you need to specify your Apple Developer Program TeamID in the build settings of an Xcode project.
25 |
26 | To do this, create a new file `DEVELOPMENT_TEAM.xcconfig` in your working copy and add the following build setting to the file:
27 |
28 | ```
29 | DEVELOPMENT_TEAM = [Your TeamID]
30 | ```
31 |
32 | The `DEVELOPMENT_TEAM.xcconfig` file should not be added to any git commit. The `.gitignore` file will prevent it from getting committed to the repository.
33 |
34 | See the file `Xcode-Config/Shared.xcconfig` for a more detailed explanation of how to set this up for this or for your own projects.
35 |
36 | A big thank-you goes to [Jeff Johnson](https://github.com/lapcat/Bonjeff) who has come up with this way of handling the `DEVELOPMENT_TEAM` issue for open-source projects.
37 |
38 | Without the above solution, every developer would have to change the `DEVELOPMENT_TEAM` for themselves and keep the change from getting into version control. Otherwise, every other developer would get conflicts and non-working builds.
39 |
40 | # Further Reading
41 |
42 | https://medium.com/sift-through-swift/finite-state-machine-in-swift-ba0958bca34f#.rv8pfot27
43 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ViewController.swift
3 | // SwiftFSM
4 | //
5 | // Created by Vishal V. Shekkar on 25/10/16.
6 | // Copyright © 2016 Vishal V. Shekkar. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class ViewController: UIViewController {
12 |
13 | @IBOutlet weak var stateLabel: UILabel!
14 |
15 | ///Represents all the states a Turnstile can be in
16 | enum TurnstileState {
17 | case locked
18 | case unlocked
19 | }
20 |
21 | ///Represents all the triggers a Turnstile can perform
22 | enum TurnstileTrigger {
23 | case insertCoin
24 | case push
25 | }
26 |
27 | ///A `typealias` used to keep line sizes small in the declarations of `turnstileMachineSchema` and `turnstileMachine`. It's not necessary to do this. This is merely done for readability.
28 | private typealias TurnstileMachineSchema = SwiftFSMSchema
29 |
30 | ///A `SwiftFSMSchema` object with associated types - `TurnstileState` and `TurnstileTrigger`
31 | private var turnstileMachineSchema = TurnstileMachineSchema(initialState: .locked) { (presentState, trigger) -> ViewController.TurnstileState in
32 | var toState: TurnstileState
33 |
34 | //The `switch` statements could be simplified further for the Turnstile as it has two loops in the graph. But, it's kept elaborated in this example to aid in understanding.
35 | switch presentState {
36 | case .locked:
37 | switch trigger {
38 | case .insertCoin:
39 | toState = .unlocked
40 | case .push:
41 | toState = .locked
42 | }
43 | case .unlocked:
44 | switch trigger {
45 | case .insertCoin:
46 | toState = .unlocked
47 | case .push:
48 | toState = .locked
49 | }
50 | }
51 | return toState
52 | }
53 |
54 | ///The optional `SwiftFSM` object.
55 | private var turnstileMachine: SwiftFSM?
56 |
57 | override func viewDidLoad() {
58 | super.viewDidLoad()
59 |
60 | //Initialization of the FSM with the schema object being passed
61 | turnstileMachine = SwiftFSM(schema: turnstileMachineSchema)
62 |
63 | //Setting the logging preference. This step is optional.
64 | turnstileMachine?.logging = .logging({(log: String) -> () in
65 | print(log) //Use any logging method you choose in this closure
66 | })
67 |
68 | //Defining the `shouldMachineTransitState` closure of the FSM. Do this only if you need to block state transition for some reason, or need to do something before the state transits. This step is optional.
69 | turnstileMachine?.shouldMachineTransitState = { (_ fromState: TurnstileState, _ trigger: TurnstileTrigger, _ toState: TurnstileState) -> Bool in
70 | return true
71 | }
72 |
73 | //Defining the `machineDidTransitState` closure of the FSM. Do this only if you need a trigger for all state changes. This step is optional.
74 | turnstileMachine?.machineDidTransitState = { (_ fromState: TurnstileState, _ trigger: TurnstileTrigger, _ toState: TurnstileState) -> () in
75 | self.stateLabel.text = "\(toState)"
76 | }
77 | }
78 |
79 | @IBAction func insertCoin(_ sender: AnyObject) {
80 | //Triggers the FSM with `.insertCoin` event.
81 | turnstileMachine?.trigger(.insertCoin)
82 | }
83 |
84 | @IBAction func pushTurnstile(_ sender: AnyObject) {
85 | //Triggers the FSM with `.push` event.
86 | turnstileMachine?.trigger(.push)
87 | }
88 |
89 | }
90 |
91 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
32 |
41 |
42 |
43 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM/SwiftFSM.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SwiftFSM.swift
3 | // SwiftFSM
4 | //
5 | // Created by Vishal V. Shekkar on 25/10/16.
6 | // Copyright © 2016 Vishal V. Shekkar. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | ///`protocol` defining the characteristics and requirements of a SwiftFSM Schema.
12 | public protocol SwiftFSMSchemaSpecification {
13 |
14 | associatedtype State
15 | associatedtype Trigger
16 |
17 | ///The state with which the FSM is initialized.
18 | var initialState: State {get}
19 |
20 | ///Specifies the entire transition logic.
21 | var transitionLogic: (_ state: State, _ trigger: Trigger) -> State {get}
22 |
23 | ///Initializes the schema object with an initial state and transition logic closure. The closure is `@escaping` because it is stored as a property in the
24 | init(initialState: State, transitionLogic: @escaping (_ state: State, _ trigger: Trigger) -> State)
25 |
26 | }
27 |
28 | ///A `struct` conforming to `SwiftFSMSchemaSpecification` `protocol`. This can be used as the default schema for the `SwiftFSM` class. Alternately, you can define any `struct` to act as the schema for the `SwiftFSM` as long as it conforms to `SwiftFSMSchemaSpecification` `protocol`.
29 | public struct SwiftFSMSchema: SwiftFSMSchemaSpecification {
30 |
31 | public typealias State = S
32 | public typealias Trigger = T
33 |
34 | public var initialState: State
35 | public var transitionLogic: (_ state: State, _ trigger: Trigger) -> State
36 |
37 | public init(initialState: State, transitionLogic: @escaping (State, Trigger) -> State) {
38 | self.initialState = initialState
39 | self.transitionLogic = transitionLogic
40 | }
41 |
42 | }
43 |
44 | ///This is the generic finite state machine class
45 | public final class SwiftFSM {
46 |
47 | ///Represents the state of the FSM at any given moment. Read-only.
48 | public private(set) var state: Schema.State
49 |
50 | ///The schema object is stored in this variable
51 | private let schema: Schema
52 |
53 | ///An optional closure that will be executed before transiting the state due to a trigger. The state will transition if the closure returns `true`. Otherwise, the FSM remains unchanged. If the closure itself is `nil`, the state change will happen as directed by the schema.
54 | public var shouldMachineTransitState: ((_ fromState: Schema.State, _ trigger: Schema.Trigger, _ toState: Schema.State) -> (Bool))?
55 |
56 | ///An optional closure that will be executed when a state transition occurs due to a trigger. The execution of this closure is subject to the result of `shouldMachineTransitState` if it's not a `nil`. Use this closure to perform actions based on any state changes.
57 | public var machineDidTransitState: ((_ fromState: Schema.State, _ trigger: Schema.Trigger, _ toState: Schema.State) -> ())?
58 |
59 | ///This `enum` represents the loggin preference. It defaults to `.logging(nil)` which uses a `print` statement to log.
60 | public var logging = SwiftFSMLogType.logging(nil)
61 |
62 | ///The only initializer for the `SwiftFSM`.
63 | /// - parameter schema: Represents all transitions of the FSM which is an object that conforms to `SwiftFSMSchemaSpecification` protocol.
64 | public init(schema: Schema) {
65 | self.schema = schema
66 | self.state = schema.initialState
67 | }
68 |
69 | ///The method which informs the FSM about a trigger. This method is solely responsible for handling all state changes, executing `shouldMachineTransitState` and `machineDidTransitState` closures and logging.
70 | /// - parameter trigger: The `Schema.Trigger` instance that represents a particular trigger.
71 | public func trigger(_ trigger: Schema.Trigger) {
72 | let toState = schema.transitionLogic(state, trigger)
73 | if let shouldMachineTransitState = shouldMachineTransitState {
74 | if shouldMachineTransitState(state, trigger, toState) {
75 | changeState(trigger: trigger, toState: toState)
76 | } else {
77 | handleLogging(fromState: state, trigger: trigger, toState: toState, transitStatus: .transitCancelled)
78 | }
79 | } else {
80 | changeState(trigger: trigger, toState: toState)
81 | }
82 | }
83 |
84 | ///Handles the state change. Modifies the `state` property and performs consecutive operations.
85 | private func changeState(trigger: Schema.Trigger, toState: Schema.State) {
86 | let oldState = state
87 | state = toState
88 | handleLogging(fromState: oldState, trigger: trigger, toState: state, transitStatus: .didTransit)
89 | machineDidTransitState?(oldState, trigger, state)
90 | }
91 |
92 | ///Handles logging of FSM transitions. It either logs using default `print` statement, or executes the closure for logging or restrains from logging based on the preference set on `logging`.
93 | private func handleLogging(fromState: Schema.State, trigger: Schema.Trigger, toState: Schema.State, transitStatus: SwiftFSMTransitStatus) {
94 | switch logging {
95 | case let .logging(some):
96 | let log = getLog(fromState: fromState, trigger: trigger, toState: toState, transitStatus: transitStatus)
97 | if let some = some { some(log) } else { print(log) }
98 | case .noLogging:
99 | break
100 | }
101 | }
102 |
103 | ///Provides the log text based on conditions.
104 | private func getLog(fromState: Schema.State, trigger: Schema.Trigger, toState: Schema.State, transitStatus: SwiftFSMTransitStatus) -> String {
105 | var log = transitStatus.rawValue
106 | log += "🔥\(trigger)🔥: \(fromState) → \(toState)"
107 | return log
108 | }
109 |
110 | }
111 |
112 | ///Specifies the logging preference to the `SwiftFSM` class.
113 | public enum SwiftFSMLogType {
114 | ///Does not log anything to the console
115 | case noLogging
116 |
117 | ///Logs every state change to the console. If the closure is `nil`, logging is done using a `print` statement. Otherwise, a `String` containing the log is passed to the closure as a parameter and that can be used to log to the console using the users' preferred means.
118 | case logging(((_ log: String) -> ())?)
119 | }
120 |
121 | ///Used internally to specify the status of transition. Aids in providing log messages based on transition status.
122 | fileprivate enum SwiftFSMTransitStatus: String {
123 | case willTransit = "Machine will transit: "
124 | case transitCancelled = "Machine transition cancelled: "
125 | case didTransit = "Machine did transit: "
126 | }
127 |
--------------------------------------------------------------------------------
/SwiftFSM/SwiftFSM.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | C9B703E51DBFCD320012912B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B703E41DBFCD320012912B /* AppDelegate.swift */; };
11 | C9B703E71DBFCD320012912B /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B703E61DBFCD320012912B /* ViewController.swift */; };
12 | C9B703EA1DBFCD320012912B /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9B703E81DBFCD320012912B /* Main.storyboard */; };
13 | C9B703EC1DBFCD320012912B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = C9B703EB1DBFCD320012912B /* Assets.xcassets */; };
14 | C9B703EF1DBFCD320012912B /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C9B703ED1DBFCD320012912B /* LaunchScreen.storyboard */; };
15 | C9B703F71DBFCD540012912B /* SwiftFSM.swift in Sources */ = {isa = PBXBuildFile; fileRef = C9B703F61DBFCD540012912B /* SwiftFSM.swift */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXFileReference section */
19 | 3D05E835202B4DD6000E0EFF /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; name = LICENSE; path = ../LICENSE; sourceTree = ""; };
20 | 3D05E839202B4EDB000E0EFF /* Shared.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Shared.xcconfig; sourceTree = ""; };
21 | 3D05E83B202B50D6000E0EFF /* DEVELOPMENT_TEAM.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = DEVELOPMENT_TEAM.xcconfig; sourceTree = ""; };
22 | C9B703E11DBFCD320012912B /* SwiftFSM.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftFSM.app; sourceTree = BUILT_PRODUCTS_DIR; };
23 | C9B703E41DBFCD320012912B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
24 | C9B703E61DBFCD320012912B /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
25 | C9B703E91DBFCD320012912B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
26 | C9B703EB1DBFCD320012912B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
27 | C9B703EE1DBFCD320012912B /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
28 | C9B703F01DBFCD320012912B /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
29 | C9B703F61DBFCD540012912B /* SwiftFSM.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwiftFSM.swift; sourceTree = ""; };
30 | /* End PBXFileReference section */
31 |
32 | /* Begin PBXFrameworksBuildPhase section */
33 | C9B703DE1DBFCD320012912B /* Frameworks */ = {
34 | isa = PBXFrameworksBuildPhase;
35 | buildActionMask = 2147483647;
36 | files = (
37 | );
38 | runOnlyForDeploymentPostprocessing = 0;
39 | };
40 | /* End PBXFrameworksBuildPhase section */
41 |
42 | /* Begin PBXGroup section */
43 | 3D05E836202B4EDB000E0EFF /* Xcode-config */ = {
44 | isa = PBXGroup;
45 | children = (
46 | 3D05E83B202B50D6000E0EFF /* DEVELOPMENT_TEAM.xcconfig */,
47 | 3D05E839202B4EDB000E0EFF /* Shared.xcconfig */,
48 | );
49 | name = "Xcode-config";
50 | path = "../Xcode-config";
51 | sourceTree = "";
52 | };
53 | C9B703D81DBFCD310012912B = {
54 | isa = PBXGroup;
55 | children = (
56 | 3D05E835202B4DD6000E0EFF /* LICENSE */,
57 | 3D05E836202B4EDB000E0EFF /* Xcode-config */,
58 | C9B703E31DBFCD320012912B /* SwiftFSM */,
59 | C9B703E21DBFCD320012912B /* Products */,
60 | );
61 | sourceTree = "";
62 | };
63 | C9B703E21DBFCD320012912B /* Products */ = {
64 | isa = PBXGroup;
65 | children = (
66 | C9B703E11DBFCD320012912B /* SwiftFSM.app */,
67 | );
68 | name = Products;
69 | sourceTree = "";
70 | };
71 | C9B703E31DBFCD320012912B /* SwiftFSM */ = {
72 | isa = PBXGroup;
73 | children = (
74 | C9B703E41DBFCD320012912B /* AppDelegate.swift */,
75 | C9B703E61DBFCD320012912B /* ViewController.swift */,
76 | C9B703F61DBFCD540012912B /* SwiftFSM.swift */,
77 | C9B703E81DBFCD320012912B /* Main.storyboard */,
78 | C9B703EB1DBFCD320012912B /* Assets.xcassets */,
79 | C9B703ED1DBFCD320012912B /* LaunchScreen.storyboard */,
80 | C9B703F01DBFCD320012912B /* Info.plist */,
81 | );
82 | path = SwiftFSM;
83 | sourceTree = "";
84 | };
85 | /* End PBXGroup section */
86 |
87 | /* Begin PBXNativeTarget section */
88 | C9B703E01DBFCD320012912B /* SwiftFSM */ = {
89 | isa = PBXNativeTarget;
90 | buildConfigurationList = C9B703F31DBFCD320012912B /* Build configuration list for PBXNativeTarget "SwiftFSM" */;
91 | buildPhases = (
92 | C9B703DD1DBFCD320012912B /* Sources */,
93 | C9B703DE1DBFCD320012912B /* Frameworks */,
94 | C9B703DF1DBFCD320012912B /* Resources */,
95 | );
96 | buildRules = (
97 | );
98 | dependencies = (
99 | );
100 | name = SwiftFSM;
101 | productName = SwiftFSM;
102 | productReference = C9B703E11DBFCD320012912B /* SwiftFSM.app */;
103 | productType = "com.apple.product-type.application";
104 | };
105 | /* End PBXNativeTarget section */
106 |
107 | /* Begin PBXProject section */
108 | C9B703D91DBFCD320012912B /* Project object */ = {
109 | isa = PBXProject;
110 | attributes = {
111 | LastSwiftUpdateCheck = 0800;
112 | LastUpgradeCheck = 0800;
113 | ORGANIZATIONNAME = "Vishal V. Shekkar";
114 | TargetAttributes = {
115 | C9B703E01DBFCD320012912B = {
116 | CreatedOnToolsVersion = 8.0;
117 | DevelopmentTeam = PZJL3H9F34;
118 | ProvisioningStyle = Automatic;
119 | };
120 | };
121 | };
122 | buildConfigurationList = C9B703DC1DBFCD320012912B /* Build configuration list for PBXProject "SwiftFSM" */;
123 | compatibilityVersion = "Xcode 3.2";
124 | developmentRegion = English;
125 | hasScannedForEncodings = 0;
126 | knownRegions = (
127 | en,
128 | Base,
129 | );
130 | mainGroup = C9B703D81DBFCD310012912B;
131 | productRefGroup = C9B703E21DBFCD320012912B /* Products */;
132 | projectDirPath = "";
133 | projectRoot = "";
134 | targets = (
135 | C9B703E01DBFCD320012912B /* SwiftFSM */,
136 | );
137 | };
138 | /* End PBXProject section */
139 |
140 | /* Begin PBXResourcesBuildPhase section */
141 | C9B703DF1DBFCD320012912B /* Resources */ = {
142 | isa = PBXResourcesBuildPhase;
143 | buildActionMask = 2147483647;
144 | files = (
145 | C9B703EF1DBFCD320012912B /* LaunchScreen.storyboard in Resources */,
146 | C9B703EC1DBFCD320012912B /* Assets.xcassets in Resources */,
147 | C9B703EA1DBFCD320012912B /* Main.storyboard in Resources */,
148 | );
149 | runOnlyForDeploymentPostprocessing = 0;
150 | };
151 | /* End PBXResourcesBuildPhase section */
152 |
153 | /* Begin PBXSourcesBuildPhase section */
154 | C9B703DD1DBFCD320012912B /* Sources */ = {
155 | isa = PBXSourcesBuildPhase;
156 | buildActionMask = 2147483647;
157 | files = (
158 | C9B703E71DBFCD320012912B /* ViewController.swift in Sources */,
159 | C9B703E51DBFCD320012912B /* AppDelegate.swift in Sources */,
160 | C9B703F71DBFCD540012912B /* SwiftFSM.swift in Sources */,
161 | );
162 | runOnlyForDeploymentPostprocessing = 0;
163 | };
164 | /* End PBXSourcesBuildPhase section */
165 |
166 | /* Begin PBXVariantGroup section */
167 | C9B703E81DBFCD320012912B /* Main.storyboard */ = {
168 | isa = PBXVariantGroup;
169 | children = (
170 | C9B703E91DBFCD320012912B /* Base */,
171 | );
172 | name = Main.storyboard;
173 | sourceTree = "";
174 | };
175 | C9B703ED1DBFCD320012912B /* LaunchScreen.storyboard */ = {
176 | isa = PBXVariantGroup;
177 | children = (
178 | C9B703EE1DBFCD320012912B /* Base */,
179 | );
180 | name = LaunchScreen.storyboard;
181 | sourceTree = "";
182 | };
183 | /* End PBXVariantGroup section */
184 |
185 | /* Begin XCBuildConfiguration section */
186 | C9B703F11DBFCD320012912B /* Debug */ = {
187 | isa = XCBuildConfiguration;
188 | baseConfigurationReference = 3D05E839202B4EDB000E0EFF /* Shared.xcconfig */;
189 | buildSettings = {
190 | ALWAYS_SEARCH_USER_PATHS = NO;
191 | CLANG_ANALYZER_NONNULL = YES;
192 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
193 | CLANG_CXX_LIBRARY = "libc++";
194 | CLANG_ENABLE_MODULES = YES;
195 | CLANG_ENABLE_OBJC_ARC = YES;
196 | CLANG_WARN_BOOL_CONVERSION = YES;
197 | CLANG_WARN_CONSTANT_CONVERSION = YES;
198 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
199 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
200 | CLANG_WARN_EMPTY_BODY = YES;
201 | CLANG_WARN_ENUM_CONVERSION = YES;
202 | CLANG_WARN_INFINITE_RECURSION = YES;
203 | CLANG_WARN_INT_CONVERSION = YES;
204 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
205 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
206 | CLANG_WARN_UNREACHABLE_CODE = YES;
207 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
208 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
209 | COPY_PHASE_STRIP = NO;
210 | DEBUG_INFORMATION_FORMAT = dwarf;
211 | ENABLE_STRICT_OBJC_MSGSEND = YES;
212 | ENABLE_TESTABILITY = YES;
213 | GCC_C_LANGUAGE_STANDARD = gnu99;
214 | GCC_DYNAMIC_NO_PIC = NO;
215 | GCC_NO_COMMON_BLOCKS = YES;
216 | GCC_OPTIMIZATION_LEVEL = 0;
217 | GCC_PREPROCESSOR_DEFINITIONS = (
218 | "DEBUG=1",
219 | "$(inherited)",
220 | );
221 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
222 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
223 | GCC_WARN_UNDECLARED_SELECTOR = YES;
224 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
225 | GCC_WARN_UNUSED_FUNCTION = YES;
226 | GCC_WARN_UNUSED_VARIABLE = YES;
227 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
228 | MTL_ENABLE_DEBUG_INFO = YES;
229 | ONLY_ACTIVE_ARCH = YES;
230 | SDKROOT = iphoneos;
231 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
232 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
233 | TARGETED_DEVICE_FAMILY = "1,2";
234 | };
235 | name = Debug;
236 | };
237 | C9B703F21DBFCD320012912B /* Release */ = {
238 | isa = XCBuildConfiguration;
239 | baseConfigurationReference = 3D05E839202B4EDB000E0EFF /* Shared.xcconfig */;
240 | buildSettings = {
241 | ALWAYS_SEARCH_USER_PATHS = NO;
242 | CLANG_ANALYZER_NONNULL = YES;
243 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
244 | CLANG_CXX_LIBRARY = "libc++";
245 | CLANG_ENABLE_MODULES = YES;
246 | CLANG_ENABLE_OBJC_ARC = YES;
247 | CLANG_WARN_BOOL_CONVERSION = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
250 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
251 | CLANG_WARN_EMPTY_BODY = YES;
252 | CLANG_WARN_ENUM_CONVERSION = YES;
253 | CLANG_WARN_INFINITE_RECURSION = YES;
254 | CLANG_WARN_INT_CONVERSION = YES;
255 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
256 | CLANG_WARN_SUSPICIOUS_MOVES = YES;
257 | CLANG_WARN_UNREACHABLE_CODE = YES;
258 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
259 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
260 | COPY_PHASE_STRIP = NO;
261 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
262 | ENABLE_NS_ASSERTIONS = NO;
263 | ENABLE_STRICT_OBJC_MSGSEND = YES;
264 | GCC_C_LANGUAGE_STANDARD = gnu99;
265 | GCC_NO_COMMON_BLOCKS = YES;
266 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
267 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
268 | GCC_WARN_UNDECLARED_SELECTOR = YES;
269 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
270 | GCC_WARN_UNUSED_FUNCTION = YES;
271 | GCC_WARN_UNUSED_VARIABLE = YES;
272 | IPHONEOS_DEPLOYMENT_TARGET = 10.0;
273 | MTL_ENABLE_DEBUG_INFO = NO;
274 | SDKROOT = iphoneos;
275 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
276 | TARGETED_DEVICE_FAMILY = "1,2";
277 | VALIDATE_PRODUCT = YES;
278 | };
279 | name = Release;
280 | };
281 | C9B703F41DBFCD320012912B /* Debug */ = {
282 | isa = XCBuildConfiguration;
283 | buildSettings = {
284 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
285 | DEVELOPMENT_TEAM = PZJL3H9F34;
286 | INFOPLIST_FILE = SwiftFSM/Info.plist;
287 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
288 | PRODUCT_BUNDLE_IDENTIFIER = "Vishal-V.-Shekkar.SwiftFSM";
289 | PRODUCT_NAME = "$(TARGET_NAME)";
290 | SWIFT_VERSION = 3.0;
291 | };
292 | name = Debug;
293 | };
294 | C9B703F51DBFCD320012912B /* Release */ = {
295 | isa = XCBuildConfiguration;
296 | buildSettings = {
297 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
298 | DEVELOPMENT_TEAM = PZJL3H9F34;
299 | INFOPLIST_FILE = SwiftFSM/Info.plist;
300 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
301 | PRODUCT_BUNDLE_IDENTIFIER = "Vishal-V.-Shekkar.SwiftFSM";
302 | PRODUCT_NAME = "$(TARGET_NAME)";
303 | SWIFT_VERSION = 3.0;
304 | };
305 | name = Release;
306 | };
307 | /* End XCBuildConfiguration section */
308 |
309 | /* Begin XCConfigurationList section */
310 | C9B703DC1DBFCD320012912B /* Build configuration list for PBXProject "SwiftFSM" */ = {
311 | isa = XCConfigurationList;
312 | buildConfigurations = (
313 | C9B703F11DBFCD320012912B /* Debug */,
314 | C9B703F21DBFCD320012912B /* Release */,
315 | );
316 | defaultConfigurationIsVisible = 0;
317 | defaultConfigurationName = Release;
318 | };
319 | C9B703F31DBFCD320012912B /* Build configuration list for PBXNativeTarget "SwiftFSM" */ = {
320 | isa = XCConfigurationList;
321 | buildConfigurations = (
322 | C9B703F41DBFCD320012912B /* Debug */,
323 | C9B703F51DBFCD320012912B /* Release */,
324 | );
325 | defaultConfigurationIsVisible = 0;
326 | defaultConfigurationName = Release;
327 | };
328 | /* End XCConfigurationList section */
329 | };
330 | rootObject = C9B703D91DBFCD320012912B /* Project object */;
331 | }
332 |
--------------------------------------------------------------------------------