├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Documentation ├── TBStateMachine.graffle └── test_setup.png ├── Example ├── Podfile ├── Podfile.lock ├── TBStateMachine.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── TBStateMachineTests.xcscheme └── Tests │ ├── Fixtures │ ├── nested.json │ ├── pseudo.json │ ├── simple.json │ └── statemachine.json │ ├── TBSMCompoundTransitionTests.m │ ├── TBSMEventTests.m │ ├── TBSMForkTests.m │ ├── TBSMJoinTests.m │ ├── TBSMJunctionTests.m │ ├── TBSMParallelStateTests.m │ ├── TBSMPseudoStateTests.m │ ├── TBSMStateMachineBuilderTests.m │ ├── TBSMStateMachineNestedTests.m │ ├── TBSMStateMachineSimpleTests.m │ ├── TBSMStateTests.m │ ├── TBSMSubStateTests.m │ ├── TBSMTransitionTests.m │ ├── TBStateMachineDebugSupportTests.m │ ├── Tests-Info.plist │ ├── Tests-Prefix.pch │ └── en.lproj │ └── InfoPlist.strings ├── Gemfile ├── Gemfile.lock ├── LICENSE ├── Pod ├── Builder │ ├── Schema │ │ └── schema.json │ ├── TBSMStateMachineBuilder.h │ └── TBSMStateMachineBuilder.m ├── Core │ ├── NSException+TBStateMachine.h │ ├── NSException+TBStateMachine.m │ ├── TBSMCompoundTransition.h │ ├── TBSMCompoundTransition.m │ ├── TBSMContainingVertex.h │ ├── TBSMEvent.h │ ├── TBSMEvent.m │ ├── TBSMEventHandler.h │ ├── TBSMEventHandler.m │ ├── TBSMFork.h │ ├── TBSMFork.m │ ├── TBSMHierarchyVertex.h │ ├── TBSMJoin.h │ ├── TBSMJoin.m │ ├── TBSMJunction.h │ ├── TBSMJunction.m │ ├── TBSMJunctionPath.h │ ├── TBSMJunctionPath.m │ ├── TBSMMacros.h │ ├── TBSMParallelState.h │ ├── TBSMParallelState.m │ ├── TBSMPseudoState.h │ ├── TBSMPseudoState.m │ ├── TBSMState+Notifications.h │ ├── TBSMState+Notifications.m │ ├── TBSMState.h │ ├── TBSMState.m │ ├── TBSMStateMachine+Notifications.h │ ├── TBSMStateMachine+Notifications.m │ ├── TBSMStateMachine.h │ ├── TBSMStateMachine.m │ ├── TBSMSubState.h │ ├── TBSMSubState.m │ ├── TBSMTransition.h │ ├── TBSMTransition.m │ ├── TBSMTransitionKind.h │ └── TBSMTransitionVertex.h └── DebugSupport │ ├── TBSMDebugLogger.h │ ├── TBSMDebugLogger.m │ ├── TBSMDebugStateMachine.h │ ├── TBSMDebugStateMachine.m │ ├── TBSMDebugSwizzler.h │ ├── TBSMDebugSwizzler.m │ ├── TBSMDebugger.h │ ├── TBSMDebugger.m │ ├── TBSMEvent+DebugSupport.h │ ├── TBSMEvent+DebugSupport.m │ ├── TBSMState+DebugSupport.h │ ├── TBSMState+DebugSupport.m │ ├── TBSMStateMachine+DebugSupport.h │ ├── TBSMStateMachine+DebugSupport.m │ ├── TBSMTransition+DebugSupport.h │ └── TBSMTransition+DebugSupport.m ├── README.md ├── TBStateMachine.podspec └── refresh.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # OS X 2 | .DS_Store 3 | 4 | # Xcode 5 | build/ 6 | *.pbxuser 7 | !default.pbxuser 8 | *.mode1v3 9 | !default.mode1v3 10 | *.mode2v3 11 | !default.mode2v3 12 | *.perspectivev3 13 | !default.perspectivev3 14 | xcuserdata 15 | *.xccheckout 16 | profile 17 | *.moved-aside 18 | DerivedData 19 | *.hmap 20 | *.ipa 21 | *.xcworkspace 22 | 23 | 24 | # Bundler 25 | .bundle 26 | 27 | # Cocoapods 28 | Pods 29 | 30 | # Slather 31 | cobertura.xml 32 | html 33 | *.gcno 34 | *.gcda 35 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | osx_image: xcode10.1 2 | language: objective-c 3 | 4 | install: 5 | - gem install bundler 6 | - bundle install --without=documentation 7 | - cd Example 8 | - bundle exec pod update 9 | - cd $TRAVIS_BUILD_DIR 10 | 11 | script: 12 | - xcodebuild test -workspace Example/TBStateMachine.xcworkspace -scheme TBStateMachineTests -sdk iphonesimulator -destination 'platform=iOS Simulator,name=iPhone 7' ONLY_ACTIVE_ARCH=NO CODE_SIGN_IDENTITY="" CODE_SIGNING_REQUIRED=NO -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # TBStateMachine CHANGELOG 2 | 3 | ### 6.10.0 4 | 5 | - add TBSMStateMachineBuilder class to configure statemachines via json 6 | 7 | ### 6.9.0 8 | 9 | - add methods to subscribe and unsubscribe to notifications 10 | 11 | ### 6.8.0 12 | 13 | - add path scheme to locate states inside the hierarchy 14 | 15 | ### 6.7.3 16 | 17 | - minor code cleanup 18 | 19 | ### 6.7.2 20 | 21 | - add recursive cleanup for transition vertexes to prevent memory leaks 22 | 23 | ### 6.7.1 24 | 25 | - Break up cyclic references between states on deallocation of TBSMStateMachine 26 | - set parentVertex on states when setting states on TBSMParallelState 27 | - set parentVertex on states when setting states on TBSMSubState 28 | 29 | ### 6.7.0 30 | 31 | - Add preprocessor macro to enumerate statemachin events 32 | 33 | ### 6.6.0 34 | 35 | - Add convenience setter for states on and TBSMSubState TBSMParallelState 36 | 37 | ### 6.5.0 38 | 39 | - Posting notification when performing internal transitions 40 | 41 | ## 6.4.1 42 | 43 | - Minor internal refactorings 44 | 45 | ## 6.4.0 46 | 47 | - Improved log output 48 | 49 | ## 6.3.1 50 | 51 | - Fixed debug output when showing remaining events in queue 52 | 53 | ## 6.3.0 54 | 55 | - Added TBSMDebugger to DebugSupport subspec for clearer debug API 56 | 57 | ## 6.2.0 58 | 59 | - Additional debug output for remaining events in queue 60 | 61 | ## 6.1.2 62 | 63 | - Minor bugfixes 64 | 65 | ## 6.1.1 66 | 67 | - Minor bugfixes 68 | 69 | ## 6.1.0 70 | 71 | - Add asynchronous logging to `DebugSupport` to prevent logging from affecting performance measuring too much. 72 | 73 | ## 6.0.0 74 | 75 | - Remove parameters `sourceState` and `targetState` from handlers and notification user info to simplify API. 76 | 77 | ## 5.15.0 78 | 79 | - Cleanup interfaces for class methods. Using instancetype now. 80 | 81 | ## 5.12.0 82 | 83 | - Remove superfluous factory methods 84 | 85 | ## 5.11.0 86 | 87 | - Rework nullability by using audited regions 88 | 89 | ## 5.10.0 90 | 91 | - Add nullability annotations and generics for improved Swift compatibility to sub spec `DebugSupport` 92 | 93 | ## 5.9.0 94 | 95 | - Bugfixes 96 | 97 | ## 5.8.2 98 | 99 | - Bugfixes 100 | 101 | ## 5.8.1 102 | 103 | - Add nullability annotations and generics for improved Swift compatibility 104 | 105 | ## 5.8.0 106 | 107 | - Add convenience method `scheduleEventNamed:data:` 108 | 109 | ## 5.7.0 110 | 111 | - Add support for tvOS 9.0 112 | 113 | ## 5.6.0 114 | 115 | - changed payload from `NSDictionary` to id 116 | 117 | ## 5.5.0 118 | 119 | - add support for watchOS 2 120 | 121 | ## 5.4.2 122 | 123 | - general house keeping and cleanup 124 | - upadated documentation 125 | 126 | ## 5.4.1 127 | 128 | - using mach_time instead of `CACurrentMediaTime` for time measurement 129 | 130 | ## 5.4.0 131 | 132 | - added junction pseudo state 133 | 134 | ## 5.3.3 135 | 136 | - simplified debug support code 137 | 138 | ## 5.3.2 139 | 140 | - fixed a bug which caused a join transition to be triggered before all source transitions were performed 141 | 142 | ## 5.3.1 143 | 144 | - fixed a bug which caused a statemachine to perform a compound transition when the event handler's target was 'nil' 145 | 146 | ## 5.3.0 147 | 148 | - support for pseudo states 149 | - implementation of fork and join pseudo states 150 | - optional `DebugSupport` subspec for logging and performance measurements 151 | 152 | ## 5.2.1 153 | 154 | - minor fixes 155 | 156 | ## 5.2.0 157 | 158 | - improved error messages 159 | 160 | ## 5.1.0 161 | 162 | - thread safety increased 163 | - event scheduling is asynchronuous now 164 | - simplified notification generation 165 | - removed event deferral 166 | 167 | ## 5.0.0 168 | 169 | - clear separation between transition types external, local, internal when registering events 170 | - local transitions 171 | - added notifications for enter and exit handlers 172 | - remove option for concurrent queue in `TBSMParallelState` 173 | - renamed `-registerEvent:` to `-addHandlerForEvent:` 174 | - improved thread safety 175 | 176 | ## 4.4.0 177 | 178 | - made changes to `TBSMStateMachine` so TBSMState class and subtypes can be inherited 179 | 180 | ## 4.3.0 181 | 182 | - made concurrent queue in `TBSMParallelState` optional 183 | 184 | ## 4.2.0 185 | 186 | - states can register multiple event handlers for the same event, guard block decides which one gets executed 187 | 188 | ## 4.1.0 189 | 190 | - fixed bug which caused `TBSMParallelState` not to setup all sub machines when performing transition into one state machine 191 | - fixed a bug which caused events not to bubble up to super states when not being handled by a sub state 192 | - reworked event handling and state switching 193 | 194 | ## 4.0.1 195 | 196 | - corrected event deferral algorithm 197 | 198 | ## 4.0.0 199 | 200 | - corrected event deferral algorithm 201 | - changed event registration / deferral API 202 | 203 | ## 3.1.0 204 | 205 | - added internal transitions 206 | - added event deferral 207 | - removed method `-unregisterEvent:` from class `TBSMState` 208 | - renamed property `states` to `stateMachines` in class `TBSMParallelState` 209 | 210 | ## 3.0.4 211 | 212 | - re-added internal dispatch queue for `TBSMParallelState` 213 | 214 | ## 3.0.3 215 | 216 | - removed internal dispatch queues 217 | 218 | ## 3.0.2 219 | 220 | - removed log messages 221 | - code cleanup 222 | 223 | ## 3.0.1 224 | 225 | - removed method `- (TBSMTransition *)handleEvent:(TBSMEvent *)event` from `TBSMNode` protocol 226 | 227 | ## 3.0.0 228 | 229 | - changed class name to use prefix `TBSM` instead of `TBStateMachine` 230 | - added TBSMSubState which derives from `TBSMState` (formerly known as `TBStateMachineState`) 231 | 232 | ## 2.0.1 233 | 234 | ### fixes 235 | 236 | - LCA handling was broken and resulted in a wrong execution sequence of exit - action - enter 237 | - corrected typos in CHANGELOG.md (this document) 238 | 239 | ## 2.0.0 240 | 241 | ### enhanced state switching: 242 | 243 | - state machine can now switch from a substate of a sub state machine deep into another submachine. Implementation uses LCA (Least Common Ancestor) - algorithm 244 | 245 | ### re-worked event handling: 246 | 247 | - events will be registered by setting target action and guard: 248 | 249 | ``` 250 | - (void)registerEvent:(TBStateMachineEvent *)event 251 | target:(id)target 252 | action:(TBStateMachineActionBlock)action 253 | guard:(TBStateMachineGuardBlock)guard; 254 | ``` 255 | 256 | - event processing follows RTC-model. The state machine will queue all events it receives until processing of the current event has finished 257 | 258 | ## 1.0.0 259 | 260 | - updated documentation 261 | - updated API 262 | 263 | ## 0.9.0 264 | 265 | - initial release 266 | -------------------------------------------------------------------------------- /Documentation/TBStateMachine.graffle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkrumow/TBStateMachine/fd7e9a9ebc8b5440b26785dda8d4e5a59a2f96fa/Documentation/TBStateMachine.graffle -------------------------------------------------------------------------------- /Documentation/test_setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jkrumow/TBStateMachine/fd7e9a9ebc8b5440b26785dda8d4e5a59a2f96fa/Documentation/test_setup.png -------------------------------------------------------------------------------- /Example/Podfile: -------------------------------------------------------------------------------- 1 | source 'https://github.com/CocoaPods/Specs.git' 2 | 3 | platform :ios, '8.0' 4 | target 'Tests' do 5 | pod 'TBStateMachine', :path => '../' 6 | pod 'TBStateMachine/Builder', :path => '../' 7 | pod 'TBStateMachine/DebugSupport', :path => '../' 8 | 9 | pod 'Specta' 10 | pod 'Expecta' 11 | end 12 | -------------------------------------------------------------------------------- /Example/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - Expecta (1.0.6) 3 | - Specta (1.0.7) 4 | - TBStateMachine (6.10.0): 5 | - TBStateMachine/Core (= 6.10.0) 6 | - TBStateMachine/Builder (6.10.0): 7 | - TBStateMachine/Core 8 | - TBStateMachine/Core (6.10.0) 9 | - TBStateMachine/DebugSupport (6.10.0): 10 | - TBStateMachine/Core 11 | 12 | DEPENDENCIES: 13 | - Expecta 14 | - Specta 15 | - TBStateMachine (from `../`) 16 | - TBStateMachine/Builder (from `../`) 17 | - TBStateMachine/DebugSupport (from `../`) 18 | 19 | SPEC REPOS: 20 | https://github.com/cocoapods/specs.git: 21 | - Expecta 22 | - Specta 23 | 24 | EXTERNAL SOURCES: 25 | TBStateMachine: 26 | :path: "../" 27 | 28 | SPEC CHECKSUMS: 29 | Expecta: 3b6bd90a64b9a1dcb0b70aa0e10a7f8f631667d5 30 | Specta: 3e1bd89c3517421982dc4d1c992503e48bd5fe66 31 | TBStateMachine: 6d90c711c401d4700154b918292f5913618a9b2f 32 | 33 | PODFILE CHECKSUM: 49783ab2ae73b624e701bb55acb71c21287cb6de 34 | 35 | COCOAPODS: 1.6.1 36 | -------------------------------------------------------------------------------- /Example/TBStateMachine.xcodeproj/xcshareddata/xcschemes/TBStateMachineTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 29 | 35 | 36 | 37 | 38 | 39 | 44 | 45 | 47 | 53 | 54 | 55 | 56 | 57 | 63 | 64 | 65 | 66 | 67 | 68 | 78 | 80 | 86 | 87 | 88 | 89 | 90 | 91 | 97 | 99 | 105 | 106 | 107 | 108 | 110 | 111 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/nested.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "states": [ 4 | { 5 | "name": "a", 6 | "states": [ 7 | { 8 | "name": "a1", 9 | "type": "state" 10 | }, 11 | { 12 | "name": "a2", 13 | "type": "state" 14 | } 15 | ], 16 | "type": "sub" 17 | }, 18 | { 19 | "name": "b", 20 | "regions": [ 21 | [ 22 | { 23 | "name": "b11", 24 | "type": "state" 25 | } 26 | ], 27 | [ 28 | { 29 | "name": "b21", 30 | "type": "state" 31 | }, 32 | { 33 | "name": "b22", 34 | "type": "state" 35 | } 36 | ] 37 | ], 38 | "type": "parallel" 39 | }, 40 | { 41 | "name": "c", 42 | "type": "state" 43 | } 44 | ], 45 | "transitions": [ 46 | { 47 | "type": "simple", 48 | "kind": "external", 49 | "name": "a1_a2", 50 | "source": "a/a1", 51 | "target": "a/a2" 52 | }, 53 | { 54 | "type": "simple", 55 | "kind": "external", 56 | "name": "a1_b", 57 | "source": "a/a1", 58 | "target": "b" 59 | }, 60 | { 61 | "type": "simple", 62 | "kind": "external", 63 | "name": "b11_c", 64 | "source": "b@0/b11", 65 | "target": "c" 66 | }, 67 | { 68 | "type": "simple", 69 | "kind": "internal", 70 | "name": "c_internal", 71 | "source": "c", 72 | "target": "c" 73 | }, 74 | { 75 | "type": "simple", 76 | "kind": "local", 77 | "name": "a_local", 78 | "source": "a", 79 | "target": "a/a1" 80 | } 81 | ] 82 | } 83 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/pseudo.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "states": [ 4 | { 5 | "name": "a", 6 | "states": [ 7 | { 8 | "name": "a1", 9 | "type": "state" 10 | }, 11 | { 12 | "name": "a2", 13 | "type": "state" 14 | } 15 | ], 16 | "type": "sub" 17 | }, 18 | { 19 | "name": "b", 20 | "regions": [ 21 | [ 22 | { 23 | "name": "b11", 24 | "type": "state" 25 | } 26 | ], 27 | [ 28 | { 29 | "name": "b21", 30 | "type": "state" 31 | }, 32 | { 33 | "name": "b22", 34 | "type": "state" 35 | } 36 | ] 37 | ], 38 | "type": "parallel" 39 | }, 40 | { 41 | "name": "c", 42 | "type": "state" 43 | } 44 | ], 45 | "transitions": [ 46 | { 47 | "type": "simple", 48 | "kind": "external", 49 | "name": "a1_a2", 50 | "source": "a/a1", 51 | "target": "a/a2" 52 | }, 53 | { 54 | "type": "compound", 55 | "name": "fork_b", 56 | "vertices": { 57 | "incoming": [ 58 | { 59 | "source": "a/a1", 60 | "name": "fork_b" 61 | } 62 | ], 63 | "outgoing": [ 64 | { 65 | "target": "b@0/b11" 66 | }, 67 | { 68 | "target": "b@1/b21" 69 | } 70 | ] 71 | }, 72 | "pseudo_state": { 73 | "type": "fork", 74 | "name": "fork_1", 75 | "region": "b" 76 | } 77 | }, 78 | { 79 | "type": "compound", 80 | "name": "join_c", 81 | "vertices": { 82 | "incoming": [ 83 | { 84 | "source": "b@0/b11", 85 | "name": "b11_join" 86 | }, 87 | { 88 | "source": "b@1/b21", 89 | "name": "b21_join" 90 | } 91 | ], 92 | "outgoing": [ 93 | { 94 | "target": "c" 95 | } 96 | ] 97 | }, 98 | "pseudo_state": { 99 | "type": "join", 100 | "name": "join_1", 101 | "region": "b" 102 | } 103 | } 104 | ] 105 | } 106 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/simple.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "states": [ 4 | { 5 | "name": "a", 6 | "type": "state" 7 | }, 8 | { 9 | "name": "b", 10 | "type": "state" 11 | }, 12 | { 13 | "name": "c", 14 | "type": "state" 15 | } 16 | ], 17 | "transitions": [ 18 | { 19 | "type": "simple", 20 | "kind": "external", 21 | "name": "a_b", 22 | "source": "a", 23 | "target": "b" 24 | }, 25 | { 26 | "type": "simple", 27 | "kind": "external", 28 | "name": "b_c", 29 | "source": "b", 30 | "target": "c" 31 | }, 32 | { 33 | "type": "simple", 34 | "kind": "external", 35 | "name": "c_a", 36 | "source": "c", 37 | "target": "a" 38 | } 39 | ] 40 | } 41 | -------------------------------------------------------------------------------- /Example/Tests/Fixtures/statemachine.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "states": [ 4 | { 5 | "name": "a", 6 | "states": [ 7 | { 8 | "name": "a1", 9 | "type": "state" 10 | }, 11 | { 12 | "name": "a2", 13 | "type": "state" 14 | }, 15 | { 16 | "name": "a3", 17 | "type": "state" 18 | } 19 | ], 20 | "type": "sub" 21 | }, 22 | { 23 | "name": "b", 24 | "states": [ 25 | { 26 | "name": "b1", 27 | "type": "state" 28 | }, 29 | { 30 | "name": "b2", 31 | "states": [ 32 | { 33 | "name": "b21", 34 | "type": "state" 35 | }, 36 | { 37 | "name": "b22", 38 | "type": "state" 39 | } 40 | ], 41 | "type": "sub" 42 | }, 43 | { 44 | "name": "b3", 45 | "regions": [ 46 | [ 47 | { 48 | "name": "b311", 49 | "type": "state" 50 | }, 51 | { 52 | "name": "b312", 53 | "type": "state" 54 | } 55 | ], 56 | [ 57 | { 58 | "name": "b321", 59 | "type": "state" 60 | }, 61 | { 62 | "name": "b322", 63 | "type": "state" 64 | } 65 | ] 66 | ], 67 | "type": "parallel" 68 | } 69 | ], 70 | "type": "sub" 71 | }, 72 | { 73 | "name": "c", 74 | "states": [ 75 | { 76 | "name": "c1", 77 | "type": "state" 78 | }, 79 | { 80 | "name": "c2", 81 | "regions": [ 82 | [ 83 | { 84 | "name": "c211", 85 | "type": "state" 86 | }, 87 | { 88 | "name": "c212", 89 | "type": "state" 90 | } 91 | ], 92 | [ 93 | { 94 | "name": "c221", 95 | "type": "state" 96 | }, 97 | { 98 | "name": "c222", 99 | "type": "state" 100 | } 101 | ] 102 | ], 103 | "type": "parallel" 104 | } 105 | ], 106 | "type": "sub" 107 | } 108 | ], 109 | "transitions": [ 110 | { 111 | "type": "simple", 112 | "kind": "external", 113 | "name": "a2_a", 114 | "source": "a/a2", 115 | "target": "a" 116 | }, 117 | { 118 | "type": "simple", 119 | "kind": "external", 120 | "name": "a_a2", 121 | "source": "a", 122 | "target": "a/a2" 123 | }, 124 | { 125 | "type": "simple", 126 | "kind": "external", 127 | "name": "a3_a1", 128 | "source": "a/a3", 129 | "target": "a/a1" 130 | }, 131 | { 132 | "type": "simple", 133 | "kind": "external", 134 | "name": "a_guard", 135 | "source": "a", 136 | "target": "b" 137 | }, 138 | { 139 | "type": "simple", 140 | "kind": "external", 141 | "name": "a3_b2", 142 | "source": "a/a3", 143 | "target": "b/b2" 144 | }, 145 | { 146 | "type": "simple", 147 | "kind": "external", 148 | "name": "a3_b22", 149 | "source": "a/a3", 150 | "target": "b/b2/b22" 151 | }, 152 | { 153 | "type": "simple", 154 | "kind": "external", 155 | "name": "b_a3", 156 | "source": "b", 157 | "target": "a/a3" 158 | }, 159 | { 160 | "type": "simple", 161 | "kind": "external", 162 | "name": "b311_a1", 163 | "source": "b/b3@0/b311", 164 | "target": "a/a1" 165 | }, 166 | { 167 | "type": "simple", 168 | "kind": "local", 169 | "name": "b22_b", 170 | "source": "b/b2/b22", 171 | "target": "b" 172 | }, 173 | { 174 | "type": "simple", 175 | "kind": "local", 176 | "name": "b_b22", 177 | "source": "b", 178 | "target": "b/b2/b22" 179 | }, 180 | { 181 | "type": "simple", 182 | "kind": "external", 183 | "name": "b_b3", 184 | "source": "b", 185 | "target": "b/b3" 186 | }, 187 | { 188 | "type": "simple", 189 | "kind": "external", 190 | "name": "a3_b322", 191 | "source": "a/a3", 192 | "target": "b/b3@1/b322" 193 | }, 194 | { 195 | "type": "compound", 196 | "name": "a_fork", 197 | "vertices": { 198 | "incoming": [ 199 | { 200 | "source": "a", 201 | "name": "a_fork" 202 | } 203 | ], 204 | "outgoing": [ 205 | { 206 | "target": "c/c2@0/c212" 207 | }, 208 | { 209 | "target": "c/c2@1/c222" 210 | } 211 | ] 212 | }, 213 | "pseudo_state": { 214 | "type": "fork", 215 | "name": "fork_1", 216 | "region": "c/c2" 217 | } 218 | }, 219 | { 220 | "type": "compound", 221 | "name": "join_c_b", 222 | "vertices": { 223 | "incoming": [ 224 | { 225 | "source": "c/c2@0/c212", 226 | "name": "c212_join" 227 | }, 228 | { 229 | "source": "c/c2@1/c222", 230 | "name": "c222_join" 231 | } 232 | ], 233 | "outgoing": [ 234 | { 235 | "target": "b" 236 | } 237 | ] 238 | }, 239 | "pseudo_state": { 240 | "type": "join", 241 | "name": "join_1", 242 | "region": "c/c2" 243 | } 244 | } 245 | ] 246 | } -------------------------------------------------------------------------------- /Example/Tests/TBSMCompoundTransitionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMCompoundTransitionTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMCompoundTransition) 12 | 13 | NSString * const EVENT_NAME_A = @"DummyEventA"; 14 | 15 | __block TBSMState *a; 16 | __block TBSMState *b; 17 | __block TBSMState *c; 18 | __block TBSMParallelState *p; 19 | __block TBSMParallelState *empty; 20 | __block TBSMFork *fork; 21 | __block TBSMJoin *join; 22 | 23 | describe(@"TBSMCompoundTransition", ^{ 24 | 25 | beforeEach(^{ 26 | 27 | a = [TBSMState stateWithName:@"a"]; 28 | b = [TBSMState stateWithName:@"b"]; 29 | c = [TBSMState stateWithName:@"c"]; 30 | p = [TBSMParallelState parallelStateWithName:@"parallel"]; 31 | empty = [TBSMParallelState parallelStateWithName:@"empty"]; 32 | 33 | p.states = @[@[a]]; 34 | fork = [TBSMFork forkWithName:@"fork"]; 35 | [fork setTargetStates:@[a, b] inRegion:p]; 36 | join = [TBSMJoin joinWithName:@"join"]; 37 | [join setSourceStates:@[a, b] inRegion:p target:c]; 38 | }); 39 | 40 | afterEach(^{ 41 | a = nil; 42 | b = nil; 43 | c = nil; 44 | p = nil; 45 | empty = nil; 46 | fork = nil; 47 | join = nil; 48 | }); 49 | 50 | it (@"returns its name from a fork transition.", ^{ 51 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:c targetPseudoState:fork action:nil guard:nil eventName:EVENT_NAME_A]; 52 | expect(transition.name).to.equal(@"c --> fork --> parallel/[a,b]"); 53 | }); 54 | 55 | it (@"returns its name from a join transition.", ^{ 56 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:nil eventName:EVENT_NAME_A]; 57 | expect(transition.name).to.equal(@"parallel/[a,b] --> join --> c"); 58 | }); 59 | 60 | it (@"returns source state.", ^{ 61 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:nil eventName:EVENT_NAME_A]; 62 | expect(transition.sourceState).to.equal(a); 63 | }); 64 | 65 | it (@"returns target pseudo state .", ^{ 66 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:nil eventName:EVENT_NAME_A]; 67 | expect(transition.targetPseudoState).to.equal(join); 68 | }); 69 | 70 | it (@"returns target state .", ^{ 71 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:nil eventName:EVENT_NAME_A]; 72 | expect(transition.targetState).to.equal(c); 73 | }); 74 | 75 | it (@"returns action block.", ^{ 76 | 77 | TBSMActionBlock action = ^(id data) {}; 78 | 79 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:action guard:nil eventName:EVENT_NAME_A]; 80 | expect(transition.action).to.equal(action); 81 | }); 82 | 83 | it (@"returns guard block.", ^{ 84 | 85 | TBSMGuardBlock guard = ^BOOL(id data) { 86 | return YES; 87 | }; 88 | 89 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:guard eventName:EVENT_NAME_A]; 90 | expect(transition.guard).to.equal(guard); 91 | }); 92 | 93 | it(@"throws a `TBSMException` when transition has ambiguous attributes.", ^{ 94 | join = [TBSMJoin joinWithName:@"join"]; 95 | [join setSourceStates:@[a] inRegion:empty target:b]; 96 | TBSMCompoundTransition *transition = [[TBSMCompoundTransition alloc] initWithSourceState:a targetPseudoState:join action:nil guard:nil eventName:EVENT_NAME_A]; 97 | 98 | expect(^{ 99 | [transition performTransitionWithData:nil]; 100 | }).to.raise(TBSMException); 101 | }); 102 | }); 103 | 104 | SpecEnd 105 | -------------------------------------------------------------------------------- /Example/Tests/TBSMEventTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEventTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 14.09.14. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMEvent) 12 | 13 | 14 | describe(@"TBSMEvent", ^{ 15 | 16 | describe(@"Exception handling on setup.", ^{ 17 | 18 | it (@"throws a TBSMException when name is an empty string.", ^{ 19 | 20 | expect(^{ 21 | [TBSMEvent eventWithName:@"" data:nil]; 22 | }).to.raise(TBSMException); 23 | 24 | }); 25 | 26 | it (@"returns its name.", ^{ 27 | TBSMEvent *event = [TBSMEvent eventWithName:@"a" data:nil]; 28 | expect(event.name).to.equal(@"a"); 29 | }); 30 | 31 | }); 32 | 33 | }); 34 | 35 | describe(@"TBSMEventHandler", ^{ 36 | 37 | describe(@"Exception handling on setup.", ^{ 38 | 39 | it (@"throws a TBSMException when name is an empty string.", ^{ 40 | 41 | TBSMState *state = [TBSMState stateWithName:@"state"]; 42 | 43 | expect(^{ 44 | TBSMEventHandler *handler = [[TBSMEventHandler alloc] initWithName:@"" target:state kind:TBSMTransitionExternal action:nil guard:nil]; 45 | handler = nil; 46 | }).to.raise(TBSMException); 47 | 48 | }); 49 | 50 | it (@"returns its name.", ^{ 51 | 52 | TBSMState *state = [TBSMState stateWithName:@"state"]; 53 | TBSMEventHandler *eventHandler = [[TBSMEventHandler alloc] initWithName:@"a" target:state kind:TBSMTransitionExternal action:nil guard:nil]; 54 | expect(eventHandler.name).to.equal(@"a"); 55 | }); 56 | }); 57 | }); 58 | 59 | SpecEnd 60 | -------------------------------------------------------------------------------- /Example/Tests/TBSMForkTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMForkTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMFork) 12 | 13 | __block TBSMState *a; 14 | __block TBSMState *b; 15 | __block TBSMParallelState *parallel; 16 | 17 | describe(@"TBSMFork", ^{ 18 | 19 | beforeEach(^{ 20 | a = [TBSMState stateWithName:@"a"]; 21 | b = [TBSMState stateWithName:@"b"]; 22 | parallel = [TBSMParallelState parallelStateWithName:@"parallel"]; 23 | parallel.states = @[@[a], @[b]]; 24 | }); 25 | 26 | afterEach(^{ 27 | a = nil; 28 | b = nil; 29 | parallel = nil; 30 | }); 31 | 32 | describe(@"Exception handling.", ^{ 33 | 34 | it (@"throws a TBSMException when name is an empty string.", ^{ 35 | 36 | expect(^{ 37 | [TBSMFork forkWithName:@""]; 38 | }).to.raise(TBSMException); 39 | 40 | }); 41 | 42 | it(@"throws a `TBSMException` when source, region or target states are invalid.", ^{ 43 | 44 | expect(^{ 45 | TBSMFork *fork = [TBSMFork forkWithName:@"Fork"]; 46 | [fork setTargetStates:@[] inRegion:parallel]; 47 | }).to.raise(TBSMException); 48 | }); 49 | }); 50 | 51 | it(@"returns its name.", ^{ 52 | TBSMFork *fork = [TBSMFork forkWithName:@"Fork"]; 53 | expect(fork.name).to.equal(@"Fork"); 54 | }); 55 | }); 56 | 57 | SpecEnd 58 | -------------------------------------------------------------------------------- /Example/Tests/TBSMJoinTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJoinTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMJoin) 12 | 13 | __block TBSMState *a; 14 | __block TBSMState *b; 15 | __block TBSMState *c; 16 | __block TBSMParallelState *parallel; 17 | 18 | describe(@"TBSMJoin", ^{ 19 | 20 | beforeEach(^{ 21 | a = [TBSMState stateWithName:@"a"]; 22 | b = [TBSMState stateWithName:@"b"]; 23 | c = [TBSMState stateWithName:@"c"]; 24 | parallel = [TBSMParallelState parallelStateWithName:@"parallel"]; 25 | parallel.states = @[@[a], @[b]]; 26 | }); 27 | 28 | afterEach(^{ 29 | a = nil; 30 | b = nil; 31 | c = nil; 32 | parallel = nil; 33 | }); 34 | 35 | describe(@"Exception handling.", ^{ 36 | 37 | it (@"throws a TBSMException when name is an empty string.", ^{ 38 | 39 | expect(^{ 40 | [TBSMJoin joinWithName:@""]; 41 | }).to.raise(TBSMException); 42 | 43 | }); 44 | 45 | it(@"throws a `TBSMException` when source, region or target states are invalid.", ^{ 46 | 47 | expect(^{ 48 | TBSMJoin *join = [TBSMJoin joinWithName:@"Join"]; 49 | [join setSourceStates:@[] inRegion:parallel target:c]; 50 | }).to.raise(TBSMException); 51 | }); 52 | }); 53 | 54 | it(@"returns its name.", ^{ 55 | TBSMJoin *join = [TBSMJoin joinWithName:@"Join"]; 56 | expect(join.name).to.equal(@"Join"); 57 | }); 58 | 59 | describe(@"managing source states.", ^{ 60 | 61 | it(@"returns YES if all source states have been joined and resets afterwards.", ^{ 62 | TBSMJoin *join = [TBSMJoin joinWithName:@"Join"]; 63 | [join setSourceStates:@[a, b] inRegion:parallel target:c]; 64 | expect([join joinSourceState:a]).to.equal(NO); 65 | expect([join joinSourceState:a]).to.equal(NO); 66 | expect([join joinSourceState:b]).to.equal(YES); 67 | 68 | expect([join joinSourceState:a]).to.equal(NO); 69 | expect([join joinSourceState:a]).to.equal(NO); 70 | expect([join joinSourceState:b]).to.equal(YES); 71 | }); 72 | }); 73 | }); 74 | 75 | SpecEnd 76 | -------------------------------------------------------------------------------- /Example/Tests/TBSMJunctionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJunctionTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 23.04.15. 6 | // Copyright (c) 2015 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMJunction) 12 | 13 | __block TBSMState *a; 14 | __block TBSMState *b; 15 | __block TBSMStateMachine *stateMachine; 16 | 17 | describe(@"TBSMJunction", ^{ 18 | 19 | beforeEach(^{ 20 | a = [TBSMState stateWithName:@"a"]; 21 | b = [TBSMState stateWithName:@"b"]; 22 | stateMachine = [TBSMStateMachine stateMachineWithName:@"stateMachine"]; 23 | stateMachine.states = @[a, b]; 24 | }); 25 | 26 | afterEach(^{ 27 | a = nil; 28 | b = nil; 29 | stateMachine = nil; 30 | }); 31 | 32 | describe(@"Exception handling.", ^{ 33 | 34 | it (@"throws a TBSMException when name is an empty string.", ^{ 35 | 36 | expect(^{ 37 | [TBSMJunction junctionWithName:@""]; 38 | }).to.raise(TBSMException); 39 | }); 40 | 41 | it (@"throws a TBSMException when guard is nil.", ^{ 42 | 43 | TBSMJunction *junction = [TBSMJunction junctionWithName:@"junction"]; 44 | 45 | expect(^{ 46 | [junction addOutgoingPathWithTarget:a action:nil guard:nil]; 47 | }).to.raise(TBSMException); 48 | }); 49 | 50 | it (@"throws a TBSMException when no outgoing path could be determined.", ^{ 51 | 52 | TBSMJunction *junction = [TBSMJunction junctionWithName:@"junction"]; 53 | [junction addOutgoingPathWithTarget:a action:nil guard:^BOOL(id data) { 54 | return NO; 55 | }]; 56 | 57 | expect(^{ 58 | [junction outgoingPathForTransition:b data:nil]; 59 | }).to.raise(TBSMException); 60 | }); 61 | }); 62 | 63 | it(@"returns its name.", ^{ 64 | TBSMJunction *junction = [TBSMJunction junctionWithName:@"Junction"]; 65 | expect(junction.name).to.equal(@"Junction"); 66 | }); 67 | }); 68 | 69 | SpecEnd 70 | -------------------------------------------------------------------------------- /Example/Tests/TBSMParallelStateTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMParallelStateTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 01.08.2014. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMParallelState) 12 | 13 | __block TBSMState *a1; 14 | __block TBSMState *a2; 15 | __block TBSMState *b1; 16 | __block TBSMState *b2; 17 | __block TBSMState *c1; 18 | __block TBSMState *c2; 19 | __block TBSMParallelState *p; 20 | 21 | describe(@"TBSMParallelState", ^{ 22 | 23 | beforeEach(^{ 24 | p = [TBSMParallelState parallelStateWithName:@"p"]; 25 | a1 = [TBSMState stateWithName:@"a1"]; 26 | a2 = [TBSMState stateWithName:@"a2"]; 27 | b1 = [TBSMState stateWithName:@"b1"]; 28 | b2 = [TBSMState stateWithName:@"b2"]; 29 | c1 = [TBSMState stateWithName:@"c1"]; 30 | c2 = [TBSMState stateWithName:@"c2"]; 31 | }); 32 | 33 | afterEach(^{ 34 | p = nil; 35 | a1 = nil; 36 | a2 = nil; 37 | b1 = nil; 38 | b2 = nil; 39 | c1 = nil; 40 | c2 = nil; 41 | }); 42 | 43 | describe(@"Exception handling on setup.", ^{ 44 | 45 | it (@"throws a TBSMException when name is an empty string.", ^{ 46 | 47 | expect(^{ 48 | [TBSMParallelState parallelStateWithName:@""]; 49 | }).to.raise(TBSMException); 50 | 51 | }); 52 | 53 | it(@"throws a TBSMException when adding objects which are not of type TBSMStateMachine.", ^{ 54 | 55 | id object = [[NSObject alloc] init]; 56 | expect(^{ 57 | p.stateMachines = @[@[], @[], object]; 58 | }).to.raise(TBSMException); 59 | }); 60 | 61 | it (@"throws a TBSMException when instance does not contain one or more stateMachines.", ^{ 62 | 63 | expect(^{ 64 | [p enter:nil targetState:nil data:nil]; 65 | }).to.raise(TBSMException); 66 | 67 | expect(^{ 68 | [p enter:nil targetStates:@[a1, b1, c1] region:p data:nil]; 69 | }).to.raise(TBSMException); 70 | 71 | expect(^{ 72 | [p exit:nil targetState:nil data:nil]; 73 | }).to.raise(TBSMException); 74 | 75 | }); 76 | 77 | }); 78 | 79 | describe(@"getters", ^{ 80 | 81 | it(@"return the stored states.", ^{ 82 | p.states = @[@[], @[]]; 83 | expect(p.stateMachines).haveCountOf(2); 84 | expect(p.stateMachines.count).equal(2); 85 | }); 86 | }); 87 | 88 | describe(@"Convenience setters", ^{ 89 | 90 | it(@"creates a state machine implicitly", ^{ 91 | 92 | TBSMState *a1 = [TBSMState stateWithName:@"a1"]; 93 | TBSMState *a2 = [TBSMState stateWithName:@"a2"]; 94 | p.states = @[@[a1], @[a2]]; 95 | 96 | expect(p.stateMachines.count).to.equal(2); 97 | 98 | TBSMStateMachine *first = p.stateMachines.firstObject; 99 | TBSMStateMachine *second = p.stateMachines.lastObject; 100 | expect(first.name).to.equal(@"pSubMachine-0"); 101 | expect(second.name).to.equal(@"pSubMachine-1"); 102 | }); 103 | }); 104 | 105 | it(@"enters and exits all initial states", ^{ 106 | 107 | p.states = @[@[a1, a2], @[b1, b2], @[c1, c2]]; 108 | 109 | [p enter:nil targetState:nil data:nil]; 110 | 111 | expect(p.stateMachines[0].currentState).to.equal(a1); 112 | expect(p.stateMachines[1].currentState).to.equal(b1); 113 | expect(p.stateMachines[2].currentState).to.equal(c1); 114 | 115 | [p exit:nil targetState:nil data:nil]; 116 | }); 117 | 118 | it(@"enters dedicated target states.", ^{ 119 | 120 | p.states = @[@[a1, a2], @[b1, b2], @[c1, c2]]; 121 | 122 | [p enter:nil targetStates:@[a2, b2] region:p data:nil]; 123 | 124 | expect(p.stateMachines[0].currentState).to.equal(a2); 125 | expect(p.stateMachines[1].currentState).to.equal(b2); 126 | expect(p.stateMachines[2].currentState).to.equal(c1); 127 | }); 128 | }); 129 | 130 | SpecEnd 131 | -------------------------------------------------------------------------------- /Example/Tests/TBSMPseudoStateTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMPseudoStateTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMPseudoState) 12 | 13 | describe(@"TBSMPseudoState", ^{ 14 | 15 | describe(@"Exception handling.", ^{ 16 | 17 | it (@"throws a TBSMException when name is an empty string.", ^{ 18 | 19 | expect(^{ 20 | TBSMPseudoState *pseudoState = [[TBSMPseudoState alloc] initWithName:@""]; 21 | pseudoState = nil; 22 | }).to.raise(TBSMException); 23 | 24 | }); 25 | }); 26 | 27 | it(@"returns its name.", ^{ 28 | TBSMPseudoState *pseudoState = [[TBSMPseudoState alloc] initWithName:@"name"]; 29 | expect(pseudoState.name).to.equal(@"name"); 30 | }); 31 | }); 32 | 33 | SpecEnd 34 | -------------------------------------------------------------------------------- /Example/Tests/TBSMStateMachineBuilderTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachineBuilderTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 12.04.18. 6 | // Copyright © 2018 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | SpecBegin(TBSMStateMachineBuilderTests) 14 | 15 | __block NSString *simple; 16 | __block NSString *nested; 17 | __block NSString *pseudo; 18 | __block TBSMStateMachine *stateMachine; 19 | 20 | describe(@"TBSMStateMachineBuilder", ^{ 21 | 22 | beforeEach(^{ 23 | simple = [[NSBundle bundleForClass:[self class]] pathForResource:@"simple" ofType:@"json"]; 24 | nested = [[NSBundle bundleForClass:[self class]] pathForResource:@"nested" ofType:@"json"]; 25 | pseudo = [[NSBundle bundleForClass:[self class]] pathForResource:@"pseudo" ofType:@"json"]; 26 | }); 27 | 28 | afterEach(^{ 29 | [stateMachine tearDown:nil]; 30 | }); 31 | 32 | it(@"builds a simple setup", ^{ 33 | 34 | stateMachine = [TBSMStateMachineBuilder buildFromFile:simple]; 35 | expect(stateMachine.name).to.equal(@"main"); 36 | expect(stateMachine.states.count).to.equal(3); 37 | 38 | TBSMState *a = stateMachine.states[0]; 39 | TBSMState *b = stateMachine.states[1]; 40 | TBSMState *c = stateMachine.states[2]; 41 | expect(a.name).to.equal(@"a"); 42 | expect(b.name).to.equal(@"b"); 43 | expect(c.name).to.equal(@"c"); 44 | 45 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 46 | [stateMachine setUp:nil]; 47 | 48 | waitUntil(^(DoneCallback done) { 49 | [stateMachine scheduleEventNamed:@"a_b" data:nil]; 50 | [stateMachine scheduleEventNamed:@"b_c" data:nil]; 51 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:@"c_a" data:nil] withCompletion:^{ 52 | done(); 53 | }]; 54 | }); 55 | 56 | expect(stateMachine.currentState).to.equal(a); 57 | }); 58 | 59 | it(@"builds a nested setup", ^{ 60 | 61 | stateMachine = [TBSMStateMachineBuilder buildFromFile:nested]; 62 | expect(stateMachine.name).to.equal(@"main"); 63 | expect(stateMachine.states.count).to.equal(3); 64 | 65 | TBSMSubState *a = stateMachine.states[0]; 66 | TBSMParallelState *b = stateMachine.states[1]; 67 | TBSMState *c = stateMachine.states[2]; 68 | expect(a.name).to.equal(@"a"); 69 | expect(b.name).to.equal(@"b"); 70 | expect(c.name).to.equal(@"c"); 71 | 72 | expect(a.stateMachine.states.count).to.equal(2); 73 | expect(b.stateMachines[0].states.count).to.equal(1); 74 | expect(b.stateMachines[1].states.count).to.equal(2); 75 | 76 | expect([stateMachine stateWithPath:@"a/a1"]).notTo.beNil(); 77 | expect([stateMachine stateWithPath:@"a/a2"]).notTo.beNil(); 78 | 79 | expect([stateMachine stateWithPath:@"b@0/b11"]).notTo.beNil(); 80 | expect([stateMachine stateWithPath:@"b@1/b21"]).notTo.beNil(); 81 | expect([stateMachine stateWithPath:@"b@1/b22"]).notTo.beNil(); 82 | 83 | expect(c.eventHandlers.count).to.equal(1); 84 | TBSMEventHandler *handler = c.eventHandlers[@"c_internal"].firstObject; 85 | expect(handler.target).to.equal(c); 86 | expect(handler.kind).to.equal(TBSMTransitionInternal); 87 | 88 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 89 | [stateMachine setUp:nil]; 90 | 91 | waitUntil(^(DoneCallback done) { 92 | [stateMachine scheduleEventNamed:@"a1_a2" data:nil]; 93 | [stateMachine scheduleEventNamed:@"a_local" data:nil]; 94 | [stateMachine scheduleEventNamed:@"a1_b" data:nil]; 95 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:@"b11_c" data:nil] withCompletion:^{ 96 | done(); 97 | }]; 98 | }); 99 | 100 | expect(stateMachine.currentState).to.equal(c); 101 | }); 102 | 103 | it(@"builds a pseudostate setup", ^{ 104 | 105 | stateMachine = [TBSMStateMachineBuilder buildFromFile:pseudo]; 106 | expect(stateMachine.name).to.equal(@"main"); 107 | expect(stateMachine.states.count).to.equal(3); 108 | 109 | TBSMSubState *a = stateMachine.states[0]; 110 | TBSMParallelState *b = stateMachine.states[1]; 111 | TBSMState *c = stateMachine.states[2]; 112 | expect(a.name).to.equal(@"a"); 113 | expect(b.name).to.equal(@"b"); 114 | expect(c.name).to.equal(@"c"); 115 | 116 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 117 | [stateMachine setUp:nil]; 118 | 119 | waitUntil(^(DoneCallback done) { 120 | [stateMachine scheduleEventNamed:@"fork_b" data:nil]; 121 | [stateMachine scheduleEventNamed:@"b11_join" data:nil]; 122 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:@"b21_join" data:nil] withCompletion:^{ 123 | done(); 124 | }]; 125 | }); 126 | 127 | expect(stateMachine.currentState).to.equal(c); 128 | }); 129 | 130 | }); 131 | SpecEnd 132 | -------------------------------------------------------------------------------- /Example/Tests/TBSMStateMachineSimpleTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachineSimpleTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 14.09.2014. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | SpecBegin(TBSMStateMachineSimple) 13 | 14 | struct StateMachineEvents { 15 | __unsafe_unretained NSString *EVENT_A; 16 | __unsafe_unretained NSString *EVENT_B; 17 | }; 18 | 19 | struct StateMachineEvents StateMachineEvents = { 20 | .EVENT_A = @"event_a", 21 | .EVENT_B = @"event_b" 22 | }; 23 | 24 | NSString * const EVENT_DATA_VALUE = @"DummyDataValue"; 25 | 26 | __block NSOperationQueue *testQueue; 27 | __block TBSMStateMachine *stateMachine; 28 | __block TBSMState *a; 29 | __block TBSMState *b; 30 | __block TBSMState *c; 31 | 32 | describe(@"TBSMStateMachine", ^{ 33 | 34 | beforeEach(^{ 35 | testQueue = [NSOperationQueue new]; 36 | testQueue.maxConcurrentOperationCount = 1; 37 | 38 | stateMachine = [TBSMStateMachine stateMachineWithName:@"StateMachine"]; 39 | 40 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 41 | 42 | a = [TBSMState stateWithName:@"a"]; 43 | b = [TBSMState stateWithName:@"b"]; 44 | c = [TBSMState stateWithName:@"c"]; 45 | }); 46 | 47 | afterEach(^{ 48 | [stateMachine tearDown:nil]; 49 | stateMachine = nil; 50 | a = nil; 51 | b = nil; 52 | c = nil; 53 | }); 54 | 55 | describe(@"Exception handling on setup.", ^{ 56 | 57 | it (@"throws a TBSMException when name is an empty string.", ^{ 58 | expect(^{ 59 | stateMachine = [TBSMStateMachine stateMachineWithName:@""]; 60 | }).to.raise(TBSMException); 61 | }); 62 | 63 | it(@"throws a TBSMException when state object is not of type TBSMState.", ^{ 64 | id object = [NSObject new]; 65 | expect(^{ 66 | stateMachine.states = @[a, b, object]; 67 | }).to.raise(TBSMException); 68 | }); 69 | 70 | it(@"throws a TBSMException when initial state does not exist in set of defined states.", ^{ 71 | stateMachine.states = @[a, b]; 72 | expect(^{ 73 | stateMachine.initialState = c; 74 | }).to.raise(TBSMException); 75 | 76 | }); 77 | 78 | it(@"throws a TBSMException when initial state has not been set on setup.", ^{ 79 | expect(^{ 80 | [stateMachine setUp:nil]; 81 | }).to.raise(TBSMException); 82 | }); 83 | 84 | it(@"throws a TBSMException when the scheduledEventsQueue is not a serial queue.", ^{ 85 | NSOperationQueue *queue = [NSOperationQueue new]; 86 | queue.maxConcurrentOperationCount = 2; 87 | 88 | expect(^{ 89 | stateMachine.scheduledEventsQueue = queue; 90 | }).to.raise(TBSMException); 91 | }); 92 | }); 93 | 94 | describe(@"Setup and configuration.", ^{ 95 | 96 | it(@"accepts a serial NSOperationQueue as scheduledEventsQueue.", ^{ 97 | NSOperationQueue *queue = [NSOperationQueue new]; 98 | queue.maxConcurrentOperationCount = 1; 99 | stateMachine.scheduledEventsQueue = queue; 100 | 101 | expect(stateMachine.scheduledEventsQueue).to.equal(queue); 102 | }); 103 | }); 104 | 105 | describe(@"Location inside hierarchy.", ^{ 106 | 107 | it(@"returns its path inside the state machine hierarchy containing all parent state machines in descending order.", ^{ 108 | TBSMSubState *s = [TBSMSubState subStateWithName:@"s"]; 109 | s.states = @[b]; 110 | 111 | TBSMParallelState *p = [TBSMParallelState parallelStateWithName:@"ParallelWrapper"]; 112 | p.states = @[@[s]]; 113 | stateMachine.states = @[p]; 114 | 115 | NSArray *path = [s.stateMachine path]; 116 | 117 | expect(path.count).to.equal(3); 118 | expect(path[0]).to.equal(stateMachine); 119 | expect(path[1]).to.equal(p.stateMachines[0]); 120 | expect(path[2]).to.equal(s.stateMachine); 121 | }); 122 | 123 | }); 124 | 125 | describe(@"getters", ^{ 126 | 127 | it(@"returns its name.", ^{ 128 | TBSMStateMachine *stateMachineXYZ = [TBSMStateMachine stateMachineWithName:@"StateMachineXYZ"]; 129 | expect(stateMachineXYZ.name).to.equal(@"StateMachineXYZ"); 130 | }); 131 | 132 | it(@"returns the stored states.", ^{ 133 | stateMachine.states = @[a, b]; 134 | expect(stateMachine.states).haveCountOf(2); 135 | expect(stateMachine.states).contain(a); 136 | expect(stateMachine.states).contain(b); 137 | }); 138 | }); 139 | 140 | describe(@"setUp:", ^{ 141 | 142 | it(@"enters initial state on set up when it has been set explicitly.", ^{ 143 | 144 | stateMachine.states = @[a, b]; 145 | stateMachine.initialState = a; 146 | 147 | [stateMachine setUp:nil]; 148 | 149 | expect(stateMachine.currentState).to.equal(stateMachine.initialState); 150 | expect(stateMachine.currentState).to.equal(a); 151 | }); 152 | 153 | it(@"enters initial state on set up when it has been set implicitly.", ^{ 154 | 155 | stateMachine.states = @[a, b]; 156 | 157 | [stateMachine setUp:nil]; 158 | 159 | expect(stateMachine.currentState).to.equal(stateMachine.initialState); 160 | expect(stateMachine.currentState).to.equal(a); 161 | }); 162 | }); 163 | 164 | describe(@"tearDown:", ^{ 165 | 166 | it(@"exits current state on tear down.", ^{ 167 | 168 | stateMachine.states = @[a, b]; 169 | [stateMachine setUp:nil]; 170 | 171 | expect(stateMachine.currentState).to.equal(a); 172 | 173 | [stateMachine tearDown:nil]; 174 | 175 | expect(stateMachine.currentState).to.beNil; 176 | }); 177 | }); 178 | 179 | describe(@"scheduleEventNamed:data:", ^{ 180 | 181 | it(@"creates an event object through convenienceMethod.", ^{ 182 | 183 | [a addHandlerForEvent:StateMachineEvents.EVENT_A target:b]; 184 | [b addHandlerForEvent:StateMachineEvents.EVENT_B target:c]; 185 | 186 | stateMachine.states = @[a, b, c]; 187 | [stateMachine setUp:nil]; 188 | 189 | waitUntil(^(DoneCallback done) { 190 | [stateMachine scheduleEventNamed:StateMachineEvents.EVENT_A data:EVENT_DATA_VALUE]; 191 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_B data:nil] withCompletion:^{ 192 | done(); 193 | }]; 194 | }); 195 | 196 | expect(stateMachine.currentState).to.equal(c); 197 | }); 198 | }); 199 | 200 | describe(@"scheduleEvent:", ^{ 201 | 202 | it(@"switches to the specified state.", ^{ 203 | 204 | [a addHandlerForEvent:StateMachineEvents.EVENT_A target:b kind:TBSMTransitionExternal]; 205 | 206 | stateMachine.states = @[a, b]; 207 | [stateMachine setUp:nil]; 208 | 209 | expect(stateMachine.currentState).to.equal(a); 210 | 211 | waitUntil(^(DoneCallback done) { 212 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:nil] withCompletion:^{ 213 | done(); 214 | }]; 215 | }); 216 | 217 | expect(stateMachine.currentState).to.equal(b); 218 | }); 219 | 220 | it(@"evaluates a guard function, exits the current state, executes transition action and enters the next state.", ^{ 221 | 222 | NSMutableString *executionSequence = [NSMutableString stringWithString:@""]; 223 | 224 | a.enterBlock = ^(id data) { 225 | [executionSequence appendString:@"enterA"]; 226 | }; 227 | 228 | a.exitBlock = ^(id data) { 229 | [executionSequence appendString:@"-exitA"]; 230 | }; 231 | 232 | b.enterBlock = ^(id data) { 233 | [executionSequence appendString:@"-enterB"]; 234 | }; 235 | 236 | [a addHandlerForEvent:StateMachineEvents.EVENT_A 237 | target:b 238 | kind:TBSMTransitionExternal 239 | action:^(id data) { 240 | [executionSequence appendString:@"-actionA"]; 241 | } 242 | guard:^BOOL(id data) { 243 | [executionSequence appendString:@"-guardA"]; 244 | return YES; 245 | }]; 246 | 247 | stateMachine.states = @[a, b]; 248 | [stateMachine setUp:nil]; 249 | 250 | waitUntil(^(DoneCallback done) { 251 | 252 | // will enter state B 253 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:nil] withCompletion:^{ 254 | done(); 255 | }]; 256 | 257 | }); 258 | 259 | expect(executionSequence).to.equal(@"enterA-guardA-exitA-actionA-enterB"); 260 | }); 261 | 262 | it(@"evaluates a guard function, and skips switching to the next state.", ^{ 263 | 264 | __block BOOL didExecuteAction = NO; 265 | 266 | [a addHandlerForEvent:StateMachineEvents.EVENT_A 267 | target:b 268 | kind:TBSMTransitionExternal 269 | action:^(id data) { 270 | didExecuteAction = YES; 271 | } 272 | guard:^BOOL(id data) { 273 | return NO; 274 | }]; 275 | 276 | stateMachine.states = @[a, b]; 277 | [stateMachine setUp:nil]; 278 | 279 | waitUntil(^(DoneCallback done) { 280 | 281 | // will not enter state B 282 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:nil] withCompletion:^{ 283 | done(); 284 | }]; 285 | }); 286 | 287 | expect(didExecuteAction).to.equal(NO); 288 | expect(stateMachine.currentState).to.equal(a); 289 | }); 290 | 291 | it(@"evaluates multiple guard functions, and switches to the next state.", ^{ 292 | 293 | __block BOOL didExecuteActionA = NO; 294 | __block BOOL didExecuteActionB = NO; 295 | 296 | [a addHandlerForEvent:StateMachineEvents.EVENT_A 297 | target:b 298 | kind:TBSMTransitionExternal 299 | action:^(id data) { 300 | didExecuteActionA = YES; 301 | } 302 | guard:^BOOL(id data) { 303 | return NO; 304 | }]; 305 | 306 | [a addHandlerForEvent:StateMachineEvents.EVENT_A 307 | target:b 308 | kind:TBSMTransitionExternal 309 | action:^(id data) { 310 | didExecuteActionB = YES; 311 | } 312 | guard:^BOOL(id data) { 313 | return YES; 314 | }]; 315 | 316 | stateMachine.states = @[a, b]; 317 | [stateMachine setUp:nil]; 318 | 319 | waitUntil(^(DoneCallback done) { 320 | 321 | // will enter state B through second transition 322 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:nil] withCompletion:^{ 323 | done(); 324 | }]; 325 | }); 326 | 327 | expect(didExecuteActionA).to.equal(NO); 328 | expect(didExecuteActionB).to.equal(YES); 329 | expect(stateMachine.currentState).to.equal(b); 330 | }); 331 | 332 | it(@"passes source and target state and event data into the enter, exit, action and guard blocks of the involved states.", ^{ 333 | 334 | __block id stateDataEnter; 335 | __block id stateDataExit; 336 | __block id actionData; 337 | __block id guardData; 338 | 339 | a.enterBlock = ^(id data) { 340 | stateDataEnter = data; 341 | }; 342 | 343 | a.exitBlock = ^(id data) { 344 | stateDataExit = data; 345 | }; 346 | 347 | a.enterBlock = ^(id data) { 348 | stateDataEnter = data; 349 | }; 350 | 351 | a.exitBlock = ^(id data) { 352 | stateDataExit = data; 353 | }; 354 | 355 | [a addHandlerForEvent:StateMachineEvents.EVENT_A 356 | target:b 357 | kind:TBSMTransitionExternal 358 | action:^(id data) { 359 | actionData = data; 360 | } 361 | guard:^BOOL(id data) { 362 | guardData = data; 363 | return YES; 364 | }]; 365 | 366 | b.enterBlock = ^(id data) { 367 | stateDataEnter = data; 368 | }; 369 | 370 | stateMachine.states = @[a, b]; 371 | [stateMachine setUp:nil]; 372 | expect(stateDataEnter).to.beNil; 373 | 374 | waitUntil(^(DoneCallback done) { 375 | 376 | // enters state B 377 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:EVENT_DATA_VALUE] withCompletion:^{ 378 | done(); 379 | }]; 380 | 381 | }); 382 | expect(stateDataExit).to.equal(EVENT_DATA_VALUE); 383 | expect(guardData).to.equal(EVENT_DATA_VALUE); 384 | expect(actionData).to.equal(EVENT_DATA_VALUE); 385 | expect(stateDataEnter).to.equal(EVENT_DATA_VALUE); 386 | }); 387 | 388 | it(@"can re-enter a state.", ^{ 389 | 390 | __block BOOL didEnterStateA = NO; 391 | __block BOOL didExitStateA = NO; 392 | 393 | a.enterBlock = ^(id data) { 394 | didEnterStateA = YES; 395 | }; 396 | 397 | a.exitBlock = ^(id data) { 398 | didExitStateA = YES; 399 | }; 400 | 401 | [a addHandlerForEvent:StateMachineEvents.EVENT_A target:a kind:TBSMTransitionExternal]; 402 | 403 | stateMachine.states = @[a, b]; 404 | 405 | [stateMachine setUp:nil]; 406 | 407 | didEnterStateA = NO; 408 | 409 | waitUntil(^(DoneCallback done) { 410 | 411 | [stateMachine scheduleEvent:[TBSMEvent eventWithName:StateMachineEvents.EVENT_A data:nil] withCompletion:^{ 412 | done(); 413 | }]; 414 | }); 415 | 416 | expect(stateMachine.currentState).to.equal(a); 417 | expect(didExitStateA).to.equal(YES); 418 | expect(didEnterStateA).to.equal(YES); 419 | }); 420 | }); 421 | 422 | describe(@"NSNotificationCenter support.", ^{ 423 | 424 | it(@"posts a notification when entering the specified state.", ^{ 425 | 426 | NSNotification *notification = [NSNotification notificationWithName:TBSMStateDidEnterNotification object:a userInfo:@{TBSMDataUserInfo:EVENT_DATA_VALUE}]; 427 | 428 | stateMachine.states = @[a]; 429 | 430 | expect(^{ 431 | [stateMachine setUp:EVENT_DATA_VALUE]; 432 | }).to.notify(notification); 433 | }); 434 | 435 | it(@"posts a notification when exiting the specified state.", ^{ 436 | 437 | NSNotification *notification = [NSNotification notificationWithName:TBSMStateDidExitNotification object:a userInfo:@{TBSMDataUserInfo:EVENT_DATA_VALUE}]; 438 | 439 | stateMachine.states = @[a]; 440 | [stateMachine setUp:nil]; 441 | 442 | expect(^{ 443 | [stateMachine tearDown:EVENT_DATA_VALUE]; 444 | }).to.notify(notification); 445 | }); 446 | 447 | it(@"posts a notification when performing an internal transition.", ^{ 448 | 449 | [a addHandlerForEvent:StateMachineEvents.EVENT_A target:a kind:TBSMTransitionInternal]; 450 | 451 | stateMachine.states = @[a]; 452 | [stateMachine setUp:nil]; 453 | 454 | __block id payload = nil; 455 | waitUntil(^(DoneCallback done) { 456 | [[NSNotificationCenter defaultCenter] addObserverForName:StateMachineEvents.EVENT_A object:a queue:testQueue usingBlock:^(NSNotification *notification) { 457 | payload = notification.userInfo[TBSMDataUserInfo]; 458 | done(); 459 | }]; 460 | 461 | [stateMachine scheduleEventNamed:StateMachineEvents.EVENT_A data:EVENT_DATA_VALUE]; 462 | }); 463 | 464 | expect(payload).to.equal(EVENT_DATA_VALUE); 465 | }); 466 | }); 467 | }); 468 | 469 | SpecEnd 470 | -------------------------------------------------------------------------------- /Example/Tests/TBSMStateTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 14.09.14. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMState) 12 | 13 | NSString * const EVENT_NAME_A = @"DummyEventA"; 14 | NSString * const EVENT_NAME_B = @"DummyEventB"; 15 | 16 | __block TBSMState *a; 17 | __block TBSMState *b; 18 | 19 | describe(@"TBSMState", ^{ 20 | 21 | beforeEach(^{ 22 | a = [TBSMState stateWithName:@"a"]; 23 | b = [TBSMState stateWithName:@"b"]; 24 | }); 25 | 26 | afterEach(^{ 27 | a = nil; 28 | b = nil; 29 | }); 30 | 31 | describe(@"Exception handling.", ^{ 32 | 33 | it (@"throws a TBSMException when name is an empty string.", ^{ 34 | 35 | expect(^{ 36 | [TBSMState stateWithName:@""]; 37 | }).to.raise(TBSMException); 38 | 39 | }); 40 | 41 | it(@"throws a `TBSMException` when attempting to add event handler which makes no sense.", ^{ 42 | 43 | expect(^{ 44 | [a addHandlerForEvent:EVENT_NAME_A target:b kind:TBSMTransitionInternal]; 45 | }).to.raise(TBSMException); 46 | 47 | expect(^{ 48 | [a addHandlerForEvent:EVENT_NAME_A target:b kind:TBSMTransitionInternal]; 49 | }).to.raise(TBSMException); 50 | }); 51 | 52 | }); 53 | 54 | it(@"returns its name.", ^{ 55 | TBSMState *stateXYZ = [TBSMState stateWithName:@"StateXYZ"]; 56 | expect(stateXYZ.name).to.equal(@"StateXYZ"); 57 | }); 58 | 59 | it(@"registers TBSMEvent instances with a given target TBSMState.", ^{ 60 | 61 | [a addHandlerForEvent:EVENT_NAME_A target:a]; 62 | 63 | NSDictionary *registeredEvents = a.eventHandlers; 64 | expect(registeredEvents.allKeys).to.haveCountOf(1); 65 | expect(registeredEvents).to.contain(EVENT_NAME_A); 66 | 67 | NSArray *eventHandlers = registeredEvents[EVENT_NAME_A]; 68 | expect(eventHandlers.count).to.equal(1); 69 | TBSMEventHandler *eventHandler = eventHandlers[0]; 70 | expect(eventHandler.target).to.equal(a); 71 | }); 72 | 73 | it(@"should return YES if an event can be handled.", ^{ 74 | 75 | [a addHandlerForEvent:EVENT_NAME_A target:a kind:TBSMTransitionInternal]; 76 | BOOL canHandle = [a hasHandlerForEvent:[TBSMEvent eventWithName:EVENT_NAME_A data:nil]]; 77 | expect(canHandle).to.equal(YES); 78 | canHandle = [a hasHandlerForEvent:[TBSMEvent eventWithName:EVENT_NAME_B data:nil]]; 79 | expect(canHandle).to.equal(NO); 80 | 81 | }); 82 | 83 | it(@"should return an array of TBSMEventHandler instances containing source and target state for a given event.", ^{ 84 | 85 | [a addHandlerForEvent:EVENT_NAME_A target:a kind:TBSMTransitionInternal]; 86 | [a addHandlerForEvent:EVENT_NAME_B target:a]; 87 | 88 | NSArray *resultA = [a eventHandlersForEvent:[TBSMEvent eventWithName:EVENT_NAME_A data:nil]]; 89 | expect(resultA).to.beNil; 90 | 91 | NSArray *resultB = [a eventHandlersForEvent:[TBSMEvent eventWithName:EVENT_NAME_B data:nil]]; 92 | expect(resultB.count).to.equal(1); 93 | TBSMEventHandler *eventHandlerB = resultB[0]; 94 | expect(eventHandlerB.target).to.equal(a); 95 | }); 96 | 97 | it(@"returns its path inside the state machine hierarchy containing all parent nodes in descending order", ^{ 98 | 99 | TBSMSubState *s = [TBSMSubState subStateWithName:@"s"]; 100 | s.states = @[b]; 101 | 102 | TBSMParallelState *p = [TBSMParallelState parallelStateWithName:@"p"]; 103 | p.states = @[@[s]]; 104 | 105 | TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:@"stateMachine"]; 106 | stateMachine.states = @[p]; 107 | 108 | NSArray *path = [b path]; 109 | 110 | expect(path.count).to.equal(6); 111 | expect(path[0]).to.equal(stateMachine); 112 | expect(path[1]).to.equal(p); 113 | expect(path[2]).to.equal(p.stateMachines.firstObject); 114 | expect(path[3]).to.equal(s); 115 | expect(path[4]).to.equal(s.stateMachine); 116 | expect(path[5]).to.equal(b); 117 | }); 118 | }); 119 | 120 | SpecEnd 121 | -------------------------------------------------------------------------------- /Example/Tests/TBSMSubStateTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMSubStateTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.09.14. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMSubState) 12 | 13 | describe(@"TBSMSubState", ^{ 14 | 15 | describe(@"Exception handling on setup.", ^{ 16 | 17 | it (@"throws a TBSMException when name is an empty string.", ^{ 18 | 19 | expect(^{ 20 | [TBSMSubState subStateWithName:@""]; 21 | }).to.raise(TBSMException); 22 | }); 23 | 24 | it(@"throws a TBSMException when adding an object which is not of type TBSMStateMachine.", ^{ 25 | 26 | id object = [[NSObject alloc] init]; 27 | TBSMSubState *subState = [TBSMSubState subStateWithName:@"subState"]; 28 | expect(^{ 29 | subState.stateMachine = object; 30 | }).to.raise(TBSMException); 31 | }); 32 | 33 | it (@"throws a TBSMException when stateMachine is nil.", ^{ 34 | 35 | TBSMState *a1 = [TBSMState stateWithName:@"a1"]; 36 | TBSMState *a2 = [TBSMState stateWithName:@"a2"]; 37 | 38 | TBSMParallelState *p = [TBSMParallelState parallelStateWithName:@"parallelstate"]; 39 | p.states = @[@[a1, a2]]; 40 | 41 | expect(^{ 42 | TBSMSubState *subState = [TBSMSubState subStateWithName:@"subState"]; 43 | [subState enter:nil targetState:nil data:nil]; 44 | }).to.raise(TBSMException); 45 | 46 | expect(^{ 47 | TBSMSubState *subState = [TBSMSubState subStateWithName:@"subState"]; 48 | [subState enter:nil targetStates:@[a1, a2] region:p data:nil]; 49 | }).to.raise(TBSMException); 50 | 51 | expect(^{ 52 | TBSMSubState *subState = [TBSMSubState subStateWithName:@"subState"]; 53 | [subState exit:nil targetState:nil data:nil]; 54 | }).to.raise(TBSMException); 55 | }); 56 | }); 57 | 58 | describe(@"Convenience setters", ^{ 59 | 60 | it(@"creates a state machine implicitly", ^{ 61 | 62 | TBSMState *a1 = [TBSMState stateWithName:@"a1"]; 63 | TBSMState *a2 = [TBSMState stateWithName:@"a2"]; 64 | TBSMSubState *subState = [TBSMSubState subStateWithName:@"subState"]; 65 | subState.states = @[a1, a2]; 66 | 67 | expect(subState.stateMachine).notTo.beNil(); 68 | expect(subState.stateMachine.name).to.equal(@"subStateSubMachine"); 69 | }); 70 | }); 71 | 72 | it(@"enters and exits all initial states", ^{ 73 | 74 | TBSMSubState *a = [TBSMSubState subStateWithName:@"a"]; 75 | TBSMState *a1 = [TBSMState stateWithName:@"a1"]; 76 | TBSMState *a2 = [TBSMState stateWithName:@"a2"]; 77 | a.states = @[a1, a2]; 78 | 79 | [a enter:nil targetState:nil data:nil]; 80 | expect(a.stateMachine.currentState).to.equal(a1); 81 | 82 | [a exit:nil targetState:nil data:nil]; 83 | expect(a.stateMachine.currentState).to.beNil; 84 | }); 85 | }); 86 | 87 | SpecEnd 88 | -------------------------------------------------------------------------------- /Example/Tests/TBSMTransitionTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransitionTests.m 3 | // TBStateMachineTests 4 | // 5 | // Created by Julian Krumow on 28.09.14. 6 | // Copyright (c) 2014-2016 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | SpecBegin(TBSMTransition) 12 | 13 | NSString * const EVENT_NAME_A = @"DummyEventA"; 14 | 15 | __block TBSMState *a; 16 | __block TBSMState *b; 17 | 18 | describe(@"TBSMTransition", ^{ 19 | 20 | beforeEach(^{ 21 | a = [TBSMState stateWithName:@"a"]; 22 | b = [TBSMState stateWithName:@"b"]; 23 | }); 24 | 25 | afterEach(^{ 26 | a = nil; 27 | b = nil; 28 | }); 29 | 30 | it (@"returns its name.", ^{ 31 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:nil kind:TBSMTransitionInternal action:nil guard:nil eventName:EVENT_NAME_A]; 32 | expect(transition.name).to.equal(@"a"); 33 | 34 | transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:nil guard:nil eventName:EVENT_NAME_A]; 35 | expect(transition.name).to.equal(@"a --> b"); 36 | }); 37 | 38 | it (@"returns source state.", ^{ 39 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:nil guard:nil eventName:EVENT_NAME_A]; 40 | expect(transition.sourceState).to.equal(a); 41 | }); 42 | 43 | it (@"returns target state.", ^{ 44 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:nil guard:nil eventName:EVENT_NAME_A]; 45 | expect(transition.targetState).to.equal(b); 46 | }); 47 | 48 | it (@"returns action block.", ^{ 49 | 50 | TBSMActionBlock action = ^(id data) { 51 | 52 | }; 53 | 54 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:action guard:nil eventName:EVENT_NAME_A]; 55 | expect(transition.action).to.equal(action); 56 | }); 57 | 58 | it (@"returns guard block.", ^{ 59 | 60 | TBSMGuardBlock guard = ^BOOL(id data) { 61 | return YES; 62 | }; 63 | 64 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:nil guard:guard eventName:EVENT_NAME_A]; 65 | expect(transition.guard).to.equal(guard); 66 | }); 67 | 68 | it(@"throws a `TBSMException` if no lca was found.", ^{ 69 | 70 | expect(^{ 71 | TBSMTransition *transition = [[TBSMTransition alloc] initWithSourceState:a targetState:b kind:TBSMTransitionExternal action:nil guard:nil eventName:EVENT_NAME_A]; 72 | [transition performTransitionWithData:nil]; 73 | }).to.raise(TBSMException); 74 | 75 | }); 76 | }); 77 | 78 | SpecEnd 79 | -------------------------------------------------------------------------------- /Example/Tests/TBStateMachineDebugSupportTests.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBStateMachineDebugSupportTests.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 04.04.15. 6 | // Copyright (c) 2015 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | SpecBegin(DebugSupport) 13 | 14 | __block TBSMStateMachine *stateMachine; 15 | __block TBSMSubState *s; 16 | __block TBSMParallelState *p; 17 | __block TBSMState *a; 18 | __block TBSMState *b; 19 | __block TBSMState *c; 20 | 21 | beforeEach(^{ 22 | 23 | stateMachine = [TBSMStateMachine stateMachineWithName:@"main"]; 24 | s = [TBSMSubState subStateWithName:@"s"]; 25 | p = [TBSMParallelState parallelStateWithName:@"p"]; 26 | a = [TBSMState stateWithName:@"a"]; 27 | b = [TBSMState stateWithName:@"b"]; 28 | c = [TBSMState stateWithName:@"c"]; 29 | 30 | p.states = @[@[b], @[c]]; 31 | s.states = @[p, a]; 32 | stateMachine.states = @[s]; 33 | [stateMachine setUp:nil]; 34 | }); 35 | 36 | afterEach(^{ 37 | 38 | [stateMachine tearDown:nil]; 39 | stateMachine = nil; 40 | }); 41 | 42 | describe(@"DebugSupport", ^{ 43 | 44 | describe(@"-activateDebugSupport", ^{ 45 | 46 | it (@"throws a TBSMException when activated on non-toplevel state machine.", ^{ 47 | 48 | expect(^{ 49 | [[TBSMDebugger sharedInstance] debugStateMachine:s.stateMachine]; 50 | }).to.raise(TBSMDebugSupportException); 51 | }); 52 | }); 53 | 54 | describe(@"-activeStateConfiguration", ^{ 55 | 56 | it(@"returns an NSArray containing all names of the currently activated states and their containing state machines.", ^{ 57 | 58 | NSString *expectedConfiguration = @"main\n\ts\n\t\tsSubMachine\n\t\t\tp\n\t\t\t\tpSubMachine-0\n\t\t\t\t\tb\n\t\t\t\tpSubMachine-1\n\t\t\t\t\tc\n"; 59 | 60 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 61 | NSString *configuration = [[TBSMDebugger sharedInstance] activeStateConfiguration]; 62 | expect(configuration).to.equal(expectedConfiguration); 63 | }); 64 | }); 65 | }); 66 | 67 | SpecEnd 68 | -------------------------------------------------------------------------------- /Example/Tests/Tests-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 | CFBundlePackageType 14 | BNDL 15 | CFBundleShortVersionString 16 | 1.0 17 | CFBundleSignature 18 | ???? 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /Example/Tests/Tests-Prefix.pch: -------------------------------------------------------------------------------- 1 | // 2 | // Prefix header 3 | // 4 | // The contents of this file are implicitly included at the beginning of every test case source file. 5 | // 6 | 7 | #ifdef __OBJC__ 8 | 9 | #define EXP_SHORTHAND 10 | #import 11 | #import 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /Example/Tests/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- 1 | /* Localized versions of Info.plist keys */ 2 | 3 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem 'cocoapods' 4 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.6) 5 | rexml 6 | activesupport (7.0.5) 7 | concurrent-ruby (~> 1.0, >= 1.0.2) 8 | i18n (>= 1.6, < 2) 9 | minitest (>= 5.1) 10 | tzinfo (~> 2.0) 11 | addressable (2.8.4) 12 | public_suffix (>= 2.0.2, < 6.0) 13 | algoliasearch (1.27.5) 14 | httpclient (~> 2.8, >= 2.8.3) 15 | json (>= 1.5.1) 16 | atomos (0.1.3) 17 | claide (1.1.0) 18 | cocoapods (1.12.1) 19 | addressable (~> 2.8) 20 | claide (>= 1.0.2, < 2.0) 21 | cocoapods-core (= 1.12.1) 22 | cocoapods-deintegrate (>= 1.0.3, < 2.0) 23 | cocoapods-downloader (>= 1.6.0, < 2.0) 24 | cocoapods-plugins (>= 1.0.0, < 2.0) 25 | cocoapods-search (>= 1.0.0, < 2.0) 26 | cocoapods-trunk (>= 1.6.0, < 2.0) 27 | cocoapods-try (>= 1.1.0, < 2.0) 28 | colored2 (~> 3.1) 29 | escape (~> 0.0.4) 30 | fourflusher (>= 2.3.0, < 3.0) 31 | gh_inspector (~> 1.0) 32 | molinillo (~> 0.8.0) 33 | nap (~> 1.0) 34 | ruby-macho (>= 2.3.0, < 3.0) 35 | xcodeproj (>= 1.21.0, < 2.0) 36 | cocoapods-core (1.12.1) 37 | activesupport (>= 5.0, < 8) 38 | addressable (~> 2.8) 39 | algoliasearch (~> 1.0) 40 | concurrent-ruby (~> 1.1) 41 | fuzzy_match (~> 2.0.4) 42 | nap (~> 1.0) 43 | netrc (~> 0.11) 44 | public_suffix (~> 4.0) 45 | typhoeus (~> 1.0) 46 | cocoapods-deintegrate (1.0.5) 47 | cocoapods-downloader (1.6.3) 48 | cocoapods-plugins (1.0.0) 49 | nap 50 | cocoapods-search (1.0.1) 51 | cocoapods-trunk (1.6.0) 52 | nap (>= 0.8, < 2.0) 53 | netrc (~> 0.11) 54 | cocoapods-try (1.2.0) 55 | colored2 (3.1.2) 56 | concurrent-ruby (1.2.2) 57 | escape (0.0.4) 58 | ethon (0.16.0) 59 | ffi (>= 1.15.0) 60 | ffi (1.15.5) 61 | fourflusher (2.3.1) 62 | fuzzy_match (2.0.4) 63 | gh_inspector (1.1.3) 64 | httpclient (2.8.3) 65 | i18n (1.14.1) 66 | concurrent-ruby (~> 1.0) 67 | json (2.6.3) 68 | minitest (5.18.0) 69 | molinillo (0.8.0) 70 | nanaimo (0.3.0) 71 | nap (1.1.0) 72 | netrc (0.11.0) 73 | public_suffix (4.0.7) 74 | rexml (3.2.5) 75 | ruby-macho (2.5.1) 76 | typhoeus (1.4.0) 77 | ethon (>= 0.9.0) 78 | tzinfo (2.0.6) 79 | concurrent-ruby (~> 1.0) 80 | xcodeproj (1.22.0) 81 | CFPropertyList (>= 2.3.3, < 4.0) 82 | atomos (~> 0.1.3) 83 | claide (>= 1.0.2, < 2.0) 84 | colored2 (~> 3.1) 85 | nanaimo (~> 0.3.0) 86 | rexml (~> 3.2.4) 87 | 88 | PLATFORMS 89 | arm64-darwin-22 90 | x86_64-darwin-22 91 | 92 | DEPENDENCIES 93 | cocoapods 94 | 95 | BUNDLED WITH 96 | 2.4.10 97 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014-2019 Julian Krumow 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pod/Builder/Schema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-07/schema#", 3 | "definitions": { 4 | "state": { 5 | "type": "object", 6 | "properties": { 7 | "name": { 8 | "type": "string" 9 | }, 10 | "type": { 11 | "type": "string" 12 | } 13 | }, 14 | "required": [ 15 | "name", 16 | "type" 17 | ] 18 | }, 19 | "sub": { 20 | "type": "object", 21 | "properties": { 22 | "name": { 23 | "type": "string" 24 | }, 25 | "states": { 26 | "type": "array", 27 | "items": { 28 | "anyOf": [ 29 | { 30 | "$ref": "#/definitions/state" 31 | }, 32 | { 33 | "$ref": "#/definitions/sub" 34 | }, 35 | { 36 | "$ref": "#/definitions/parallel" 37 | } 38 | ] 39 | } 40 | }, 41 | "type": { 42 | "type": "string" 43 | } 44 | }, 45 | "required": [ 46 | "name", 47 | "type", 48 | "states" 49 | ] 50 | }, 51 | "parallel": { 52 | "type": "object", 53 | "properties": { 54 | "name": { 55 | "type": "string" 56 | }, 57 | "regions": { 58 | "type": "array", 59 | "items": { 60 | "type": "array", 61 | "items": { 62 | "anyOf": [ 63 | { 64 | "$ref": "#/definitions/state" 65 | }, 66 | { 67 | "$ref": "#/definitions/sub" 68 | }, 69 | { 70 | "$ref": "#/definitions/parallel" 71 | } 72 | ] 73 | } 74 | } 75 | }, 76 | "type": { 77 | "type": "string" 78 | } 79 | }, 80 | "required": [ 81 | "name", 82 | "type", 83 | "regions" 84 | ] 85 | }, 86 | "transition": { 87 | "type": "object", 88 | "properties": { 89 | "kind": { 90 | "type": "string" 91 | }, 92 | "name": { 93 | "type": "string" 94 | }, 95 | "source": { 96 | "type": "string" 97 | }, 98 | "target": { 99 | "type": "string" 100 | } 101 | }, 102 | "required": [ 103 | "name", 104 | "kind", 105 | "type", 106 | "source", 107 | "target" 108 | ] 109 | }, 110 | "compound_transition": { 111 | "type": "object", 112 | "properties": { 113 | "type": { 114 | "type": "string" 115 | }, 116 | "name": { 117 | "type": "string" 118 | }, 119 | "vertices": { 120 | "type": "object", 121 | "properties": { 122 | "incoming": { 123 | "type": "array", 124 | "items": { 125 | "type": "object", 126 | "properties": { 127 | "source": { 128 | "type": "string" 129 | }, 130 | "name": { 131 | "type": "string" 132 | } 133 | }, 134 | "required": [ 135 | "source", 136 | "name" 137 | ] 138 | } 139 | }, 140 | "outgoing": { 141 | "type": "array", 142 | "items": { 143 | "type": "object", 144 | "properties": { 145 | "target": { 146 | "type": "string" 147 | } 148 | }, 149 | "required": [ 150 | "target" 151 | ] 152 | } 153 | } 154 | } 155 | }, 156 | "pseudo_state": { 157 | "type": "object", 158 | "properties": { 159 | "type": { 160 | "type": "string" 161 | }, 162 | "name": { 163 | "type": "string" 164 | }, 165 | "region": { 166 | "type": "string" 167 | } 168 | }, 169 | "required": [ 170 | "name", 171 | "type", 172 | "region" 173 | ] 174 | } 175 | }, 176 | "required": [ 177 | "name", 178 | "type", 179 | "vertices", 180 | "pseudo_state" 181 | ] 182 | } 183 | }, 184 | "type": "object", 185 | "properties": { 186 | "name": { 187 | "type": "string" 188 | }, 189 | "states": { 190 | "type": "array", 191 | "items": { 192 | "anyOf": [ 193 | { 194 | "$ref": "#/definitions/state" 195 | }, 196 | { 197 | "$ref": "#/definitions/sub" 198 | }, 199 | { 200 | "$ref": "#/definitions/parallel" 201 | } 202 | ] 203 | } 204 | }, 205 | "transitions": { 206 | "type": "array", 207 | "items": { 208 | "anyOf": [ 209 | { 210 | "$ref": "#/definitions/transition" 211 | }, 212 | { 213 | "$ref": "#/definitions/compound_transition" 214 | } 215 | ] 216 | } 217 | } 218 | } 219 | } -------------------------------------------------------------------------------- /Pod/Builder/TBSMStateMachineBuilder.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachineBuilder.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 12.04.18. 6 | // 7 | 8 | #import 9 | 10 | @class TBSMStateMachine; 11 | @interface TBSMStateMachineBuilder : NSObject 12 | 13 | + (TBSMStateMachine *)buildFromFile:(NSString *)file; 14 | @end 15 | -------------------------------------------------------------------------------- /Pod/Builder/TBSMStateMachineBuilder.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachineBuilder.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 12.04.18. 6 | // 7 | 8 | #import "TBSMStateMachineBuilder.h" 9 | #import "TBSMStateMachine.h" 10 | 11 | @implementation TBSMStateMachineBuilder 12 | 13 | + (TBSMStateMachine *)buildFromFile:(NSString *)file 14 | { 15 | NSDictionary *data = [self loadFile:file]; 16 | TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:data[@"name"]]; 17 | stateMachine.states = [self buildStates:data[@"states"]]; 18 | [self configureTransitions:data forStateMachine:stateMachine]; 19 | return stateMachine; 20 | } 21 | 22 | + (NSDictionary *)loadFile:(NSString *)file 23 | { 24 | NSData *json = [NSData dataWithContentsOfFile:file]; 25 | if (json == nil) { 26 | return nil; 27 | } 28 | NSError *error = nil; 29 | NSDictionary *data = (NSDictionary *)[NSJSONSerialization JSONObjectWithData:json options:kNilOptions error:&error]; 30 | if (error) { 31 | return nil; 32 | } 33 | return data; 34 | } 35 | 36 | + (NSArray *)buildStates:(NSArray *)data 37 | { 38 | NSMutableArray *states = [NSMutableArray new]; 39 | [data enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull entry, NSUInteger index, BOOL * _Nonnull stop) { 40 | TBSMState *state = [self buildState:entry]; 41 | [states addObject:state]; 42 | }]; 43 | return states; 44 | } 45 | 46 | + (TBSMState *)buildState:(NSDictionary *)data 47 | { 48 | NSString *type = data[@"type"]; 49 | if ([type isEqualToString:@"state"]) { 50 | return [TBSMState stateWithName:data[@"name"]]; 51 | } 52 | if ([type isEqualToString:@"sub"]) { 53 | return [self buildSub:data]; 54 | } 55 | if ([type isEqualToString:@"parallel"]) { 56 | return [self buildParallel:data]; 57 | } 58 | return nil; 59 | } 60 | 61 | + (TBSMSubState *)buildSub:(NSDictionary *)data 62 | { 63 | TBSMSubState *state = [TBSMSubState subStateWithName:data[@"name"]]; 64 | state.states = [self buildStates:data[@"states"]]; 65 | return state; 66 | } 67 | 68 | + (TBSMParallelState *)buildParallel:(NSDictionary *)data 69 | { 70 | TBSMParallelState *state = [TBSMParallelState parallelStateWithName:data[@"name"]]; 71 | NSArray *regionData = data[@"regions"]; 72 | NSMutableArray *regions = [NSMutableArray new]; 73 | [regionData enumerateObjectsUsingBlock:^(NSArray * _Nonnull entry, NSUInteger idx, BOOL * _Nonnull stop) { 74 | NSArray *states = [self buildStates:entry]; 75 | [regions addObject:states]; 76 | }]; 77 | [state setStates:regions]; 78 | return state; 79 | } 80 | 81 | + (void)configureTransitions:(NSDictionary *)data forStateMachine:(TBSMStateMachine *)stateMachine 82 | { 83 | NSArray *transitionConfigurations = data[@"transitions"]; 84 | [transitionConfigurations enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull item, NSUInteger index, BOOL * _Nonnull stop) { 85 | if ([item[@"type"] isEqualToString:@"simple"]) { 86 | [self configureSimpleTransition:item forStateMachine:stateMachine]; 87 | } 88 | if ([item[@"type"] isEqualToString:@"compound"]) { 89 | [self configureCompoundTransition:item forStateMachine:stateMachine]; 90 | } 91 | }]; 92 | } 93 | 94 | + (void)configureSimpleTransition:(NSDictionary *)data forStateMachine:(TBSMStateMachine *)stateMachine 95 | { 96 | NSString *kindData = data[@"kind"]; 97 | TBSMTransitionKind kind = TBSMTransitionExternal; 98 | if ([kindData isEqualToString:@"internal"]) { 99 | kind = TBSMTransitionInternal; 100 | } 101 | if ([kindData isEqualToString:@"local"]) { 102 | kind = TBSMTransitionLocal; 103 | } 104 | TBSMState *source = [stateMachine stateWithPath:data[@"source"]]; 105 | TBSMState *target = [stateMachine stateWithPath:data[@"target"]]; 106 | [source addHandlerForEvent:data[@"name"] target:target kind:kind]; 107 | } 108 | 109 | + (void)configureCompoundTransition:(NSDictionary *)data forStateMachine:(TBSMStateMachine *)stateMachine 110 | { 111 | NSDictionary *pseudoState = data[@"pseudo_state"]; 112 | if ([pseudoState[@"type"] isEqualToString:@"fork"]) { 113 | [self configureForkTransition:data forStateMachine:stateMachine]; 114 | } 115 | if ([pseudoState[@"type"] isEqualToString:@"join"]) { 116 | [self configureJoinTransition:data forStateMachine:stateMachine]; 117 | } 118 | } 119 | 120 | + (void)configureForkTransition:(NSDictionary *)data forStateMachine:(TBSMStateMachine *)stateMachine 121 | { 122 | NSDictionary *pseudoState = data[@"pseudo_state"]; 123 | NSString *forkName = pseudoState[@"name"]; 124 | NSString *regionPath = pseudoState[@"region"]; 125 | 126 | NSDictionary *vertices = data[@"vertices"]; 127 | NSArray *incoming = vertices[@"incoming"]; 128 | NSArray *outgoing = vertices[@"outgoing"]; 129 | 130 | NSDictionary *incomingFirst = incoming.firstObject; 131 | NSString *sourceName = incomingFirst[@"name"]; 132 | NSString *sourcePath = incomingFirst[@"source"]; 133 | 134 | NSMutableArray *targets = [NSMutableArray new]; 135 | [outgoing enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull entry, NSUInteger idx, BOOL * _Nonnull stop) { 136 | NSString *targetPath = entry[@"target"]; 137 | TBSMState *target = [stateMachine stateWithPath:targetPath]; 138 | [targets addObject:target]; 139 | }]; 140 | 141 | TBSMFork *fork = [TBSMFork forkWithName:forkName]; 142 | TBSMState *source = [stateMachine stateWithPath:sourcePath]; 143 | [source addHandlerForEvent:sourceName target:fork]; 144 | 145 | TBSMParallelState *region = (TBSMParallelState *)[stateMachine stateWithPath:regionPath]; 146 | [fork setTargetStates:targets inRegion:region]; 147 | } 148 | 149 | + (void)configureJoinTransition:(NSDictionary *)data forStateMachine:(TBSMStateMachine *)stateMachine 150 | { 151 | NSDictionary *pseudoState = data[@"pseudo_state"]; 152 | NSString *joinName = pseudoState[@"name"]; 153 | NSString *regionPath = pseudoState[@"region"]; 154 | 155 | NSDictionary *vertices = data[@"vertices"]; 156 | NSArray *incoming = vertices[@"incoming"]; 157 | NSArray *outgoing = vertices[@"outgoing"]; 158 | 159 | NSDictionary *outgoingFirst = outgoing.firstObject; 160 | NSString *targetPath = outgoingFirst[@"target"]; 161 | 162 | TBSMJoin *join = [TBSMJoin joinWithName:joinName]; 163 | TBSMState *target = [stateMachine stateWithPath:targetPath]; 164 | TBSMParallelState *region = (TBSMParallelState *)[stateMachine stateWithPath:regionPath]; 165 | 166 | NSMutableArray *sources = [NSMutableArray new]; 167 | [incoming enumerateObjectsUsingBlock:^(NSDictionary * _Nonnull entry, NSUInteger idx, BOOL * _Nonnull stop) { 168 | NSString *sourceName = entry[@"name"]; 169 | NSString *sourcePath = entry[@"source"]; 170 | TBSMState *source = [stateMachine stateWithPath:sourcePath]; 171 | [source addHandlerForEvent:sourceName target:join]; 172 | [sources addObject:source]; 173 | }]; 174 | 175 | [join setSourceStates:sources inRegion:region target:target]; 176 | } 177 | 178 | @end 179 | -------------------------------------------------------------------------------- /Pod/Core/NSException+TBStateMachine.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSException+TBSM.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | FOUNDATION_EXPORT NSString * const TBSMException; 14 | 15 | /** 16 | * This category adds class methods to create NSException instances thrown by the TBStateMachine library. 17 | */ 18 | @interface NSException (TBStateMachine) 19 | 20 | /** 21 | * Thrown when an object is not of type `TBSMState`. 22 | * 23 | * The `reason:` will contain a description of the object. 24 | * 25 | * @param object The object in question. 26 | * 27 | * @return The `NSException` instance. 28 | */ 29 | + (NSException *)tbsm_notOfTypeStateException:(id)object; 30 | 31 | /** 32 | * Thrown when a specified `TBSMState` instance does not exist in the state machine. 33 | * 34 | * The `reason:` will contain the name of the state. 35 | * 36 | * @param stateName The name of the specified `TBSMState`. 37 | * 38 | * @return The `NSException` instance. 39 | */ 40 | + (NSException *)tbsm_nonExistingStateException:(NSString *)stateName; 41 | 42 | /** 43 | * Thrown when no initial state has been set on the state machine. 44 | * 45 | * The `reason:` will contain the name of the state machine. 46 | * 47 | * @param stateMachineName The name of the specified `TBSMState`. 48 | * 49 | * @return The `NSException` instance. 50 | */ 51 | + (NSException *)tbsm_noInitialStateException:(NSString *)stateMachineName; 52 | 53 | /** 54 | * Thrown when no name was given to a `TBSMState` instance. 55 | * 56 | * @return The `NSException` instance. 57 | */ 58 | + (NSException *)tbsm_noNameForStateException; 59 | 60 | /** 61 | * Thrown when no name was given to a pseudo state instance. 62 | * 63 | * @return The `NSException` instance. 64 | */ 65 | + (NSException *)tbsm_noNameForPseudoStateException; 66 | 67 | /** 68 | * Thrown when no name was given to a `TBSMEvent` instance. 69 | * 70 | * @return The `NSException` instance. 71 | */ 72 | + (NSException *)tbsm_noNameForEventException; 73 | 74 | /** 75 | * Thrown when a given object is not of type `TBSMStateMachine`. 76 | * 77 | * The `reason:` will contain a description of the object. 78 | * 79 | * @param object The object in question. 80 | * 81 | * @return The `NSException` instance. 82 | */ 83 | + (NSException *)tbsm_notAStateMachineException:(id)object; 84 | 85 | /** 86 | * Thrown when a `TBSMSubState` or `TBSMParallelState` was instanciated without a sub-machine instance. 87 | * 88 | * @param stateMachineName The name of the specified `TBSMStateMachine` instance. 89 | * 90 | * @return The `NSException` instance. 91 | */ 92 | + (NSException *)tbsm_missingStateMachineException:(NSString *)stateMachineName; 93 | 94 | /** 95 | * Thrown when no least common ancestor could be found for a given transition. 96 | * 97 | * @param transitionName The name of the transition. 98 | * 99 | * @return The `NSException` instance. 100 | */ 101 | + (NSException *)tbsm_noLcaForTransition:(NSString *)transitionName; 102 | 103 | /** 104 | * Thrown when an event handler has been added with contradicting or missing transition attributes. 105 | * 106 | * @param eventName The name of the specified event. 107 | * @param sourceState The name of the source state. 108 | * @param targetState The name of the target state. 109 | * 110 | * @return The `NSException` instance. 111 | */ 112 | + (NSException *)tbsm_ambiguousTransitionAttributes:(NSString *)eventName source:(nullable NSString *)sourceState target:(nullable NSString *)targetState; 113 | 114 | /** 115 | * Thrown when a compound transition is not well contructed. 116 | * 117 | * @param pseudoStateName The name of the pseudo state. 118 | * 119 | * @return The `NSException` instance. 120 | */ 121 | + (NSException *)tbsm_ambiguousCompoundTransitionAttributes:(NSString *)pseudoStateName; 122 | 123 | /** 124 | * Thrown when no outgoing path from a junction pseudo state could be determined. 125 | * 126 | * @param junctionName The name of the junction pseudo state. 127 | * 128 | * @return The `NSException` instance. 129 | */ 130 | + (NSException *)tbsm_noOutgoingJunctionPathException:(NSString *)junctionName; 131 | 132 | /** 133 | * Thrown when an NSOperaionQueue has been set which is not serial. 134 | * 135 | * @param queueName The name of the queue. 136 | * 137 | * @return The `NSException` instance. 138 | */ 139 | + (NSException *)tbsm_noSerialQueueException:(NSString *)queueName; 140 | 141 | /** 142 | * Thrown when a specified path could not be resolved to an existing state. 143 | * 144 | * @param path The path that coud not be resolved. 145 | * 146 | * @return The `NSException` instance. 147 | */ 148 | + (NSException *)tbsm_invalidPath:(NSString *)path; 149 | 150 | @end 151 | NS_ASSUME_NONNULL_END 152 | -------------------------------------------------------------------------------- /Pod/Core/NSException+TBStateMachine.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSException+TBStateMachine.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "NSException+TBStateMachine.h" 10 | 11 | 12 | NSString * const TBSMException = @"TBSMException"; 13 | 14 | static NSString * const TBSMNotOfTypeStateExceptionReason = @"The specified object '%@' must be of type TBSMState."; 15 | static NSString * const TBSMNonExistingStateExceptionReason = @"The specified state '%@' does not exist."; 16 | static NSString * const TBSMNoInitialStateExceptionReason = @"Initial state needs to be set on state machine '%@'."; 17 | static NSString * const TBSMNoNameForStateExceptionReason = @"State needs to have a valid name."; 18 | static NSString * const TBSMNoNameForPseudoStateExceptionReason = @"PseudoState needs to have a valid name."; 19 | static NSString * const TBSMNoNameForEventExceptionReason = @"Event needs to have a valid name."; 20 | static NSString * const TBSMNotAStateMachineExceptionReason = @"The specified object '%@' is not of type TBSMStateMachine."; 21 | static NSString * const TBSMMissingStateMachineExceptionReason = @"Containing state '%@' needs to be set up with a valid TBSMStateMachine instance."; 22 | static NSString * const TBSMNoLcaForTransitionExceptionReason = @"No transition possible for transition '%@'."; 23 | static NSString * const TBSMAmbiguousTransitionAttributesReason = @"Ambiguous transition attributes for event '%@' source '%@' target '%@'."; 24 | static NSString * const TBSMAmbiguousCompoundTransitionAttributesReason = @"Ambiguous compound transition attributes for pseudo state '%@'."; 25 | static NSString * const TBSMNoOutgoingJunctionPathReason = @"No outgoing path determined for junction '%@'."; 26 | static NSString * const TBSMNoSerialQueueExceptionReason = @"The specified queue is not a serial queue '%@'."; 27 | static NSString * const TBSMInvalidPathExceptionReason = @"Invalid path: '%@'."; 28 | 29 | @implementation NSException (TBStateMachine) 30 | 31 | + (NSException *)tbsm_notOfTypeStateException:(id)object 32 | { 33 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNotOfTypeStateExceptionReason, object] userInfo:nil]; 34 | } 35 | 36 | + (NSException *)tbsm_nonExistingStateException:(NSString *)stateName 37 | { 38 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNonExistingStateExceptionReason, stateName] userInfo:nil]; 39 | } 40 | 41 | + (NSException *)tbsm_noInitialStateException:(NSString *)stateMachineName 42 | { 43 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNoInitialStateExceptionReason, stateMachineName] userInfo:nil]; 44 | } 45 | 46 | + (NSException *)tbsm_noNameForStateException 47 | { 48 | return [NSException exceptionWithName:TBSMException reason:TBSMNoNameForStateExceptionReason userInfo:nil]; 49 | } 50 | 51 | + (NSException *)tbsm_noNameForPseudoStateException 52 | { 53 | return [NSException exceptionWithName:TBSMException reason:TBSMNoNameForPseudoStateExceptionReason userInfo:nil]; 54 | } 55 | 56 | + (NSException *)tbsm_noNameForEventException 57 | { 58 | return [NSException exceptionWithName:TBSMException reason:TBSMNoNameForEventExceptionReason userInfo:nil]; 59 | } 60 | 61 | + (NSException *)tbsm_notAStateMachineException:(id)object 62 | { 63 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNotAStateMachineExceptionReason, object] userInfo:nil]; 64 | } 65 | 66 | + (NSException *)tbsm_missingStateMachineException:(NSString *)stateName 67 | { 68 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMMissingStateMachineExceptionReason, stateName] userInfo:nil]; 69 | } 70 | 71 | + (NSException *)tbsm_noLcaForTransition:(NSString *)transitionName 72 | { 73 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNoLcaForTransitionExceptionReason, transitionName] userInfo:nil]; 74 | } 75 | 76 | + (NSException *)tbsm_ambiguousTransitionAttributes:(NSString *)eventName source:(NSString *)sourceState target:(NSString *)targetState 77 | { 78 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMAmbiguousTransitionAttributesReason, eventName, sourceState, targetState] userInfo:nil]; 79 | } 80 | 81 | + (NSException *)tbsm_ambiguousCompoundTransitionAttributes:(NSString *)pseudoStateName 82 | { 83 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMAmbiguousCompoundTransitionAttributesReason, pseudoStateName] userInfo:nil]; 84 | } 85 | 86 | + (NSException *)tbsm_noOutgoingJunctionPathException:(NSString *)junctionName 87 | { 88 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNoOutgoingJunctionPathReason, junctionName] userInfo:nil]; 89 | } 90 | 91 | + (NSException *)tbsm_noSerialQueueException:(NSString *)queueName 92 | { 93 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMNoSerialQueueExceptionReason, queueName] userInfo:nil]; 94 | } 95 | 96 | + (NSException *)tbsm_invalidPath:(NSString *)path 97 | { 98 | return [NSException exceptionWithName:TBSMException reason:[NSString stringWithFormat:TBSMInvalidPathExceptionReason, path] userInfo:nil]; 99 | } 100 | 101 | @end 102 | -------------------------------------------------------------------------------- /Pod/Core/TBSMCompoundTransition.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMCompoundTransition.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMTransition.h" 10 | #import "TBSMTransitionVertex.h" 11 | #import "TBSMPseudoState.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 16 | * This class represents a compound transition between two states. 17 | */ 18 | @interface TBSMCompoundTransition : TBSMTransition 19 | 20 | /** 21 | * The target pseudo state. 22 | */ 23 | @property (nonatomic, strong) TBSMPseudoState *targetPseudoState; 24 | 25 | /** 26 | * Initializes a `TBSMCompoundTransition` instance from a given source and target state, action and guard. 27 | * 28 | * @param sourceState The specified source state. 29 | * @param targetPseudoState The specified target pseudostate. 30 | * @param action The action associated with this transition. 31 | * @param guard The guard function associated with the transition. 32 | * 33 | * @return The initialized compound transition instance. 34 | */ 35 | - (instancetype)initWithSourceState:(TBSMState *)sourceState 36 | targetPseudoState:(TBSMPseudoState *)targetPseudoState 37 | action:(nullable TBSMActionBlock)action 38 | guard:(nullable TBSMGuardBlock)guard 39 | eventName:(NSString *)eventName; 40 | @end 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /Pod/Core/TBSMCompoundTransition.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMCompoundTransition.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMCompoundTransition.h" 10 | #import "TBSMStateMachine.h" 11 | #import "TBSMState.h" 12 | #import "TBSMFork.h" 13 | #import "TBSMJoin.h" 14 | #import "TBSMJunction.h" 15 | 16 | @interface TBSMCompoundTransition () 17 | 18 | @end 19 | 20 | @implementation TBSMCompoundTransition 21 | 22 | - (instancetype)initWithSourceState:(TBSMState *)sourceState 23 | targetPseudoState:(TBSMPseudoState *)targetPseudoState 24 | action:(TBSMActionBlock)action 25 | guard:(TBSMGuardBlock)guard 26 | eventName:(NSString *)eventName 27 | { 28 | self = [super init]; 29 | if (self) { 30 | self.sourceState = sourceState; 31 | self.targetPseudoState = targetPseudoState; 32 | self.targetState = targetPseudoState.targetState; 33 | self.action = action; 34 | self.guard = guard; 35 | self.eventName = eventName; 36 | } 37 | return self; 38 | } 39 | 40 | - (NSString *)name 41 | { 42 | NSString *source = nil; 43 | NSString *target = nil; 44 | if ([self.targetPseudoState isKindOfClass:[TBSMJoin class]]) { 45 | TBSMJoin *join = (TBSMJoin *)self.targetPseudoState; 46 | source = [NSString stringWithFormat:@"%@/[%@]", join.region.name, [[join.sourceStates valueForKeyPath:@"name"] componentsJoinedByString:@","]]; 47 | target = self.targetState.name; 48 | } 49 | if ([self.targetPseudoState isKindOfClass:[TBSMFork class]]) { 50 | TBSMFork *fork = (TBSMFork *)self.targetPseudoState; 51 | source = self.sourceState.name; 52 | target = [NSString stringWithFormat:@"%@/[%@]", fork.region.name, [[fork.targetStates valueForKeyPath:@"name"] componentsJoinedByString:@","]]; 53 | } 54 | if ([self.targetPseudoState isKindOfClass:[TBSMJunction class]]) { 55 | TBSMJunction *junction = (TBSMJunction *)self.targetPseudoState; 56 | source = self.sourceState.name; 57 | target = [NSString stringWithFormat:@"[%@]", [[junction.targetStates valueForKeyPath:@"name"] componentsJoinedByString:@","]]; 58 | } 59 | return [NSString stringWithFormat:@"%@ --> %@ --> %@", source, self.targetPseudoState.name, target]; 60 | } 61 | 62 | - (BOOL)performTransitionWithData:(id)data 63 | { 64 | if ([self canPerformTransitionWithData:data]) { 65 | if ([self.targetPseudoState isKindOfClass:[TBSMFork class]]) { 66 | [self _performForkTransitionWithData:data]; 67 | } else if ([self.targetPseudoState isKindOfClass:[TBSMJoin class]]) { 68 | [self _performJoinTransitionWithData:data]; 69 | } else if ([self.targetPseudoState isKindOfClass:[TBSMJunction class]]) { 70 | [self _performJunctionTransitionWithData:data]; 71 | } 72 | return YES; 73 | } 74 | return NO; 75 | } 76 | 77 | - (void)_performForkTransitionWithData:(id)data 78 | { 79 | TBSMFork *fork = (TBSMFork *)self.targetPseudoState; 80 | [self _validatePseudoState:fork states:fork.targetStates region:fork.region]; 81 | TBSMStateMachine *lca = [self findLeastCommonAncestor]; 82 | [lca switchState:self.sourceState targetStates:fork.targetStates region:(TBSMParallelState *)fork.targetState action:self.action data:data]; 83 | } 84 | 85 | - (void)_performJoinTransitionWithData:(id)data 86 | { 87 | TBSMJoin *join = (TBSMJoin *)self.targetPseudoState; 88 | [self _validatePseudoState:join states:join.sourceStates region:join.region]; 89 | if ([join joinSourceState:self.sourceState]) { 90 | TBSMStateMachine *lca = [self findLeastCommonAncestor]; 91 | [lca switchState:self.sourceState targetState:self.targetState action:nil data:data]; 92 | } 93 | } 94 | 95 | - (void)_performJunctionTransitionWithData:(id)data 96 | { 97 | TBSMJunction *junction = (TBSMJunction *)self.targetPseudoState; 98 | TBSMJunctionPath *outgoingPath = [junction outgoingPathForTransition:self.sourceState data:data]; 99 | self.targetState = outgoingPath.targetState; 100 | TBSMStateMachine *lca = [self findLeastCommonAncestor]; 101 | TBSMActionBlock compoundAction = ^(id data) { 102 | if (self.action) { 103 | self.action(data); 104 | } 105 | if (outgoingPath.action) { 106 | outgoingPath.action(data); 107 | } 108 | }; 109 | [lca switchState:self.sourceState targetState:self.targetState action:compoundAction data:data]; 110 | } 111 | 112 | - (void)_validatePseudoState:(TBSMPseudoState *)pseudoState states:(NSArray *)states region:(TBSMParallelState *)region 113 | { 114 | for (TBSMState *state in states) { 115 | if (![state.path containsObject:region]) { 116 | @throw [NSException tbsm_ambiguousCompoundTransitionAttributes:pseudoState.name]; 117 | } 118 | } 119 | } 120 | 121 | @end 122 | -------------------------------------------------------------------------------- /Pod/Core/TBSMContainingVertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMContainingVertex.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 23.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMHierarchyVertex.h" 11 | 12 | @class TBSMParallelState; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | * This protocol describes a subtype of `TBSMHierarchyVertex` in the state machine hierarchy 18 | * which can contain other `TBSMHierarchyVertex`es. 19 | */ 20 | @protocol TBSMContainingVertex 21 | 22 | /** 23 | * Enters a group of specified states inside a region. 24 | * 25 | * @param sourceState The source state. 26 | * @param targetStates The target states inside the specified region. 27 | * @param region The target region. 28 | * @param data The payload data. 29 | */ 30 | - (void)enter:(nullable TBSMState *)sourceState targetStates:(NSArray<__kindof TBSMState *> *)targetStates region:(TBSMParallelState *)region data:(nullable id)data; 31 | 32 | /** 33 | * Receives a specified `TBSMEvent` instance. 34 | * 35 | * @param event The given `TBSMEvent` instance. 36 | * 37 | * @return `YES` if the event has been handled. 38 | */ 39 | - (BOOL)handleEvent:(TBSMEvent *)event; 40 | 41 | @end 42 | NS_ASSUME_NONNULL_END 43 | -------------------------------------------------------------------------------- /Pod/Core/TBSMEvent.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEvent.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | * This class represents an event in a state machine. 15 | */ 16 | @interface TBSMEvent : NSObject 17 | 18 | /** 19 | * The event's name. 20 | */ 21 | @property (nonatomic, copy, readonly) NSString *name; 22 | 23 | /** 24 | * The event's payload. 25 | */ 26 | @property (nonatomic, strong, nullable) id data; 27 | 28 | /** 29 | * Creates a `TBSMEvent` instance from a given name. 30 | * 31 | * Throws a `TBSMException` when name is nil or an empty string. 32 | * 33 | * @param name The specified event name. 34 | * @param data Optional payload data. 35 | * 36 | * @return The event instance. 37 | */ 38 | + (instancetype)eventWithName:(NSString *)name data:(nullable id)data; 39 | 40 | /** 41 | * Initializes a `TBSMEvent` with a specified name. 42 | * 43 | * Throws a `TBSMException` when name is nil or an empty string. 44 | * 45 | * @param name The name of this event. Must be unique. 46 | * @param data Optional payload data. 47 | * 48 | * @return An initialized `TBSMEvent` instance. 49 | */ 50 | - (instancetype)initWithName:(NSString *)name data:(nullable id)data; 51 | 52 | @end 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /Pod/Core/TBSMEvent.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEvent.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMEvent.h" 10 | #import "NSException+TBStateMachine.h" 11 | 12 | @implementation TBSMEvent 13 | 14 | + (instancetype)eventWithName:(NSString *)name data:(id)data 15 | { 16 | return [[[self class] alloc] initWithName:name data:data]; 17 | } 18 | 19 | - (instancetype)initWithName:(NSString *)name data:(id)data 20 | { 21 | if (name == nil || [name isEqualToString:@""]) { 22 | @throw [NSException tbsm_noNameForEventException]; 23 | } 24 | self = [super init]; 25 | if (self) { 26 | _name = name.copy; 27 | _data = data; 28 | } 29 | return self; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /Pod/Core/TBSMEventHandler.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEventHandler.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 07.09.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMTransition.h" 11 | #import "TBSMState.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 16 | * This class represents an event handler object. 17 | * It stores information associated with a given event registered on a `TBSMState` instance. 18 | */ 19 | @interface TBSMEventHandler : NSObject 20 | 21 | /** 22 | * The event's name. 23 | */ 24 | @property (nonatomic, copy) NSString *name; 25 | 26 | /** 27 | * The target vertex of the transition triggered by the event. 28 | */ 29 | @property (nonatomic, strong) id target; 30 | 31 | /** 32 | * The kind of transition to perform. 33 | */ 34 | @property (nonatomic, assign) TBSMTransitionKind kind; 35 | 36 | /** 37 | * The action of the transition triggered by the event. 38 | */ 39 | @property (nonatomic, copy, nullable) TBSMActionBlock action; 40 | 41 | /** 42 | * The guard function of the transition triggered by the event. 43 | */ 44 | @property (nonatomic, copy, nullable) TBSMGuardBlock guard; 45 | 46 | /** 47 | * Initializes a `TBSMEventHandler` from a given event name, target, action and guard. 48 | * 49 | * Throws a `TBSMException` when name is nil or an empty string. 50 | * 51 | * @param name The name of this event. Must be unique. 52 | * @param target The target vertex. 53 | * @param kind The kind of transition. 54 | * @param action The action. 55 | * @param guard The guard function. 56 | * 57 | * @return An initialized `TBSMEventHandler` instance. 58 | */ 59 | - (instancetype)initWithName:(NSString *)name target:(id )target kind:(TBSMTransitionKind)kind action:(nullable TBSMActionBlock)action guard:(nullable TBSMGuardBlock)guard; 60 | 61 | @end 62 | NS_ASSUME_NONNULL_END 63 | -------------------------------------------------------------------------------- /Pod/Core/TBSMEventHandler.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEventHandler.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 07.09.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMEventHandler.h" 10 | #import "NSException+TBStateMachine.h" 11 | 12 | 13 | @implementation TBSMEventHandler 14 | 15 | - (instancetype)initWithName:(NSString *)name target:(id )target kind:(TBSMTransitionKind)kind action:(TBSMActionBlock)action guard:(TBSMGuardBlock)guard 16 | { 17 | if (name == nil || [name isEqualToString:@""]) { 18 | @throw [NSException tbsm_noNameForEventException]; 19 | } 20 | self = [super init]; 21 | if (self) { 22 | self.name = name; 23 | self.target = target; 24 | self.kind = kind; 25 | self.action = action; 26 | self.guard = guard; 27 | } 28 | return self; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /Pod/Core/TBSMFork.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMFork.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMPseudoState.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class TBSMState; 15 | @class TBSMParallelState; 16 | 17 | /** 18 | * This class represents a 'fork' pseudo state in a state machine. 19 | */ 20 | @interface TBSMFork : TBSMPseudoState 21 | 22 | @property (nonatomic, strong, readonly) TBSMParallelState *region; 23 | 24 | /** 25 | * Creates a `TBSMFork` instance from a given name. 26 | * 27 | * Throws a `TBSMException` when name is nil or an empty string. 28 | * 29 | * @param name The specified fork name. 30 | * 31 | * @return The fork instance. 32 | */ 33 | + (instancetype)forkWithName:(NSString *)name; 34 | 35 | /** 36 | * The fork's target states inside the region. 37 | * 38 | * @return An array containing the target states. 39 | */ 40 | - (NSArray<__kindof TBSMState *> *)targetStates; 41 | 42 | /** 43 | * Sets the target states for the fork transition. 44 | * 45 | * Throws a `TBSMException` when parameters are invalid. 46 | * 47 | * @param targetStates The states to enter. 48 | * @param region The containing region. 49 | */ 50 | - (void)setTargetStates:(NSArray<__kindof TBSMState *> *)targetStates inRegion:(TBSMParallelState *)region; 51 | 52 | @end 53 | NS_ASSUME_NONNULL_END 54 | -------------------------------------------------------------------------------- /Pod/Core/TBSMFork.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMFork.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMFork.h" 10 | #import "TBSMParallelState.h" 11 | 12 | @interface TBSMFork () 13 | @property (nonatomic, strong) NSArray *priv_targetStates; 14 | @end 15 | 16 | @implementation TBSMFork 17 | 18 | + (instancetype)forkWithName:(NSString *)name 19 | { 20 | return [[[self class] alloc] initWithName:name]; 21 | } 22 | 23 | - (TBSMState *)targetState 24 | { 25 | return self.region; 26 | } 27 | 28 | - (NSArray *)targetStates 29 | { 30 | return self.priv_targetStates; 31 | } 32 | 33 | - (void)setTargetStates:(NSArray *)targetStates inRegion:(TBSMParallelState *)region 34 | { 35 | if (targetStates == nil || targetStates.count == 0 || region == nil) { 36 | @throw [NSException tbsm_ambiguousCompoundTransitionAttributes:self.name]; 37 | } 38 | _priv_targetStates = targetStates; 39 | _region = region; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /Pod/Core/TBSMHierarchyVertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMHierarchyVertex.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMTransition.h" 11 | #import "TBSMEvent.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | /** 16 | * This protocol defines a vertex in a state model hierarchy. 17 | * 18 | * Classes which implement this protocol can be managed inside a state model heriarchy. 19 | */ 20 | @protocol TBSMHierarchyVertex 21 | 22 | /** 23 | * Returns the vertex's name. 24 | * 25 | * Classes which implement this method must return a unique name. 26 | * 27 | * @return The name as a string. 28 | */ 29 | - (NSString *)name; 30 | 31 | /** 32 | * Returns its path inside the state machine hierarchy containing all parent vertexes in descending order. 33 | * 34 | * @return An array containing all parent vertexes. 35 | */ 36 | - (NSMutableArray *> *)path; 37 | 38 | /** 39 | * Returns the parent vertex in the state machine hierarchy. 40 | * 41 | * @return The parent `TBSMHierarchyVertex`. 42 | */ 43 | - (nullable id)parentVertex; 44 | 45 | /** 46 | * Sets the parent vertex in the state machine hierarchy. 47 | * 48 | * @param parentVertex The parent vertex. 49 | */ 50 | - (void)setParentVertex:(nullable id)parentVertex; 51 | 52 | /** 53 | * Removes all transition vertexes to break up possible cyclic references between vertexes. 54 | */ 55 | - (void)removeTransitionVertexes; 56 | 57 | /** 58 | * Executes the enter block of the state. 59 | * 60 | * If you overwrite this method you will need to call the super implementation. 61 | * 62 | * @param sourceState The source state. 63 | * @param targetState The target state. 64 | * @param data The payload data. 65 | */ 66 | - (void)enter:(nullable TBSMState *)sourceState targetState:(nullable TBSMState *)targetState data:(nullable id)data; 67 | 68 | /** 69 | * Executes the exit block of the state. 70 | * 71 | * If you overwrite this method you will need to call the super implementation. 72 | * 73 | * @param sourceState The source state. 74 | * @param targetState The target state. 75 | * @param data The payload data. 76 | */ 77 | - (void)exit:(nullable TBSMState *)sourceState targetState:(nullable TBSMState *)targetState data:(nullable id)data; 78 | 79 | @end 80 | NS_ASSUME_NONNULL_END 81 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJoin.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJoin.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMPseudoState.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @class TBSMState; 15 | @class TBSMParallelState; 16 | 17 | /** 18 | * This class represents a 'join' pseudo state in a state machine. 19 | */ 20 | @interface TBSMJoin : TBSMPseudoState 21 | 22 | @property (nonatomic, strong, readonly) TBSMParallelState *region; 23 | 24 | /** 25 | * Creates a `TBSMJoin` instance from a given name. 26 | * 27 | * Throws a `TBSMException` when name is nil or an empty string. 28 | * 29 | * @param name The specified join name. 30 | * 31 | * @return The join instance. 32 | */ 33 | + (instancetype)joinWithName:(NSString *)name; 34 | 35 | /** 36 | * The join's source states inside the region. 37 | * 38 | * @return An array containing the source states. 39 | */ 40 | - (NSArray<__kindof TBSMState *> *)sourceStates; 41 | 42 | /** 43 | * Sets the source states of the join transition. 44 | * 45 | * Throws a `TBSMException` when parameters are invalid. 46 | * 47 | * @param sourceStates An Array of TBSMState objects. 48 | * @param region The orthogonal region containing the source states. 49 | * @param target The target state. 50 | */ 51 | - (void)setSourceStates:(NSArray<__kindof TBSMState *> *)sourceStates inRegion:(TBSMParallelState *)region target:(TBSMState *)target; 52 | 53 | /** 54 | * Performs the transition towards the join pseudostate for a given source state. 55 | * If all source states have been handled the transition switches to the target state. 56 | * 57 | * @param sourceState The source state to join. 58 | * 59 | * @return `YES` if the complete compound transition has been performed. 60 | */ 61 | - (BOOL)joinSourceState:(TBSMState *)sourceState; 62 | 63 | @end 64 | NS_ASSUME_NONNULL_END 65 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJoin.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJoin.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 20.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMJoin.h" 10 | #import "TBSMParallelState.h" 11 | 12 | @interface TBSMJoin () 13 | @property (nonatomic, strong) NSArray *priv_sourceStates; 14 | @property (nonatomic, strong) NSMutableSet *joinedSourceStates; 15 | @property (nonatomic, strong) TBSMState *target; 16 | @end 17 | 18 | @implementation TBSMJoin 19 | 20 | + (instancetype)joinWithName:(NSString *)name 21 | { 22 | return [[[self class] alloc] initWithName:name]; 23 | } 24 | 25 | - (instancetype)initWithName:(NSString *)name 26 | { 27 | self = [super initWithName:name]; 28 | if (self) { 29 | _joinedSourceStates = [NSMutableSet new]; 30 | } 31 | return self; 32 | } 33 | 34 | - (TBSMState *)targetState 35 | { 36 | return self.target; 37 | } 38 | 39 | - (NSArray *)sourceStates 40 | { 41 | return self.priv_sourceStates.copy; 42 | } 43 | 44 | - (void)setSourceStates:(NSArray *)sourceStates inRegion:(TBSMParallelState *)region target:(TBSMState *)target 45 | { 46 | if (sourceStates == nil || sourceStates.count == 0 || region == nil || target == nil) { 47 | @throw [NSException tbsm_ambiguousCompoundTransitionAttributes:self.name]; 48 | } 49 | _priv_sourceStates = sourceStates; 50 | _region = region; 51 | _target = target; 52 | } 53 | 54 | - (BOOL)joinSourceState:(TBSMState *)sourceState 55 | { 56 | if ([self.joinedSourceStates containsObject:sourceState]) { 57 | return NO; 58 | } 59 | [self.joinedSourceStates addObject:sourceState]; 60 | if ([self.joinedSourceStates isEqualToSet:[NSSet setWithArray:self.priv_sourceStates]] == NO) { 61 | return NO; 62 | } 63 | [self.joinedSourceStates removeAllObjects]; 64 | return YES; 65 | } 66 | 67 | @end 68 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJunction.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJunction.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 23.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMPseudoState.h" 10 | #import "TBSMJunctionPath.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | * This class represents a 'junction' pseudo state in a state machine. 16 | */ 17 | @interface TBSMJunction : TBSMPseudoState 18 | 19 | /** 20 | * Creates a `TBSMJunction` instance from a given name. 21 | * 22 | * Throws a `TBSMException` when name is nil or an empty string. 23 | * 24 | * @param name The specified junction name. 25 | * 26 | * @return The junction instance. 27 | */ 28 | + (instancetype)junctionWithName:(NSString *)name; 29 | 30 | /** 31 | * The junction's target states. 32 | * 33 | * @return An array containing the target states. 34 | */ 35 | - (NSArray<__kindof TBSMState *> *)targetStates; 36 | 37 | /** 38 | * Adds an outgoing path to the junction. 39 | * 40 | * @param target The target state. 41 | * @param action The action to perform. 42 | * @param guard The guard to evaluate for this path. 43 | */ 44 | - (void)addOutgoingPathWithTarget:(TBSMState *)target action:(nullable TBSMActionBlock)action guard:(nullable TBSMGuardBlock)guard; 45 | 46 | /** 47 | * Returns the outgoing path of the junction after evaluating all guards. 48 | * 49 | * Throws a `TBSMException` when no target state could be found. 50 | * 51 | * @param source The source state to transition from. 52 | * @param data The payload data. 53 | * 54 | * @return The outgoing path. 55 | */ 56 | - (TBSMJunctionPath *)outgoingPathForTransition:(TBSMState *)source data:(nullable id)data; 57 | 58 | @end 59 | NS_ASSUME_NONNULL_END 60 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJunction.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJunction.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 23.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMJunction.h" 10 | 11 | @interface TBSMJunction () 12 | @property (nonatomic, strong) NSMutableArray *outgoingPaths; 13 | @end 14 | 15 | @implementation TBSMJunction 16 | 17 | + (instancetype)junctionWithName:(NSString *)name 18 | { 19 | return [[[self class] alloc] initWithName:name]; 20 | } 21 | 22 | - (instancetype)initWithName:(NSString *)name 23 | { 24 | self = [super initWithName:name]; 25 | if (self) { 26 | _outgoingPaths = [NSMutableArray new]; 27 | } 28 | return self; 29 | } 30 | 31 | - (NSArray *)targetStates 32 | { 33 | return [self.outgoingPaths valueForKeyPath:@"targetState"]; 34 | } 35 | 36 | - (void)addOutgoingPathWithTarget:(TBSMState *)target action:(TBSMActionBlock)action guard:(TBSMGuardBlock)guard 37 | { 38 | if (target == nil || guard == nil) { 39 | @throw [NSException tbsm_ambiguousCompoundTransitionAttributes:self.name]; 40 | } 41 | TBSMJunctionPath *outgoingPath = [TBSMJunctionPath new]; 42 | outgoingPath.targetState = target; 43 | outgoingPath.action = action; 44 | outgoingPath.guard = guard; 45 | [self.outgoingPaths addObject:outgoingPath]; 46 | } 47 | 48 | - (TBSMJunctionPath *)outgoingPathForTransition:(TBSMState *)source data:(id)data 49 | { 50 | for (TBSMJunctionPath *outgoingPath in self.outgoingPaths) { 51 | if (outgoingPath.guard(data)) { 52 | return outgoingPath; 53 | } 54 | } 55 | @throw [NSException tbsm_noOutgoingJunctionPathException:self.name]; 56 | } 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJunctionPath.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJunctionPath.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 24.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMTransition.h" 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | /** 15 | * This class represents a wrapper class for an outgoing path of a `TBSMJunction`. 16 | */ 17 | @interface TBSMJunctionPath : NSObject 18 | 19 | /** 20 | * The target state of this path. 21 | */ 22 | @property (nonatomic, strong) TBSMState *targetState; 23 | 24 | /** 25 | * The action block associated with this path. 26 | */ 27 | @property (nonatomic, copy, nullable) TBSMActionBlock action; 28 | 29 | /** 30 | * The guard block associated with this path. 31 | */ 32 | @property (nonatomic, copy, nullable) TBSMGuardBlock guard; 33 | 34 | @end 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /Pod/Core/TBSMJunctionPath.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMJunctionPath.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 24.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMJunctionPath.h" 10 | 11 | @implementation TBSMJunctionPath 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Pod/Core/TBSMMacros.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMMacros.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 28.09.17. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #ifndef TBSMMacros_h 10 | #define TBSMMacros_h 11 | 12 | #define StateMachineEvent(name) \ 13 | ^(StateMachineEvent event) { \ 14 | switch (event) { \ 15 | case name: \ 16 | default: \ 17 | return @#name; \ 18 | } \ 19 | }(name) 20 | 21 | #endif /* TBSMMacros_h */ 22 | -------------------------------------------------------------------------------- /Pod/Core/TBSMParallelState.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMParallelState.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBSMState.h" 12 | #import "TBSMContainingVertex.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | * This class wraps multiple `TBSMStateMachine` instances to an orthogonal region. 18 | */ 19 | @interface TBSMParallelState : TBSMState 20 | 21 | /** 22 | * Creates a `TBSMParallelState` instance from a given name. 23 | * 24 | * Throws a `TBSMException` when name is nil or an empty string. 25 | * 26 | * @param name The specified parallel wrapper name. 27 | * 28 | * @return The parallel wrapper instance. 29 | */ 30 | + (instancetype)parallelStateWithName:(NSString *)name; 31 | 32 | /** 33 | * Initializes a `TBSMParallelState` with a specified name. 34 | * 35 | * Throws a `TBSMException` when name is nil or an empty string. 36 | * 37 | * @param name The name of this wrapper. Must be unique. 38 | * 39 | * @return An initialized `TBSMParallelState` instance. 40 | */ 41 | - (instancetype)initWithName:(NSString *)name; 42 | 43 | /** 44 | * Returns the state machines the parallel wrapper manages. 45 | * 46 | * @return An NSArray containing all `TBSMStateMachine` instances. 47 | */ 48 | - (NSArray *)stateMachines; 49 | 50 | /** 51 | * Sets the `TBSMStateMachine` instances to wrap. 52 | * 53 | * Throws `TBSMException` if the instances are not of type `TBSMStateMachine`. 54 | * 55 | * @param stateMachines An array of `TBSMStateMachine` instances. 56 | */ 57 | - (void)setStateMachines:(NSArray *)stateMachines; 58 | 59 | /** 60 | * Sets all states the parallel state will manage. First state in each array wil be set as initialState. 61 | * Creates state machines implicitly. 62 | * 63 | * Throws `TBSMException` if states are not of type `TBSMState`. 64 | * 65 | * @param states An `NSArray` containing arrays with state objects for the corresponding region. 66 | */ 67 | - (void)setStates:(NSArray *> *)states; 68 | 69 | @end 70 | NS_ASSUME_NONNULL_END 71 | -------------------------------------------------------------------------------- /Pod/Core/TBSMParallelState.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMParallelState.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMParallelState.h" 10 | #import "TBSMStateMachine.h" 11 | #import "NSException+TBStateMachine.h" 12 | 13 | @interface TBSMParallelState () 14 | @property (nonatomic, strong) NSMutableArray *priv_parallelStateMachines; 15 | @end 16 | 17 | @implementation TBSMParallelState 18 | 19 | @synthesize parentVertex = _parentVertex; 20 | 21 | + (instancetype)parallelStateWithName:(NSString *)name 22 | { 23 | return [[[self class] alloc] initWithName:name]; 24 | } 25 | 26 | - (instancetype)initWithName:(NSString *)name 27 | { 28 | self = [super initWithName:name]; 29 | if (self) { 30 | _priv_parallelStateMachines = [NSMutableArray new]; 31 | } 32 | return self; 33 | } 34 | 35 | - (NSArray *)stateMachines 36 | { 37 | return [NSArray arrayWithArray:self.priv_parallelStateMachines]; 38 | } 39 | 40 | - (void)setStateMachines:(NSArray *)stateMachines 41 | { 42 | [self.priv_parallelStateMachines removeAllObjects]; 43 | 44 | for (TBSMStateMachine *stateMachine in stateMachines) { 45 | if (![stateMachine isKindOfClass:[TBSMStateMachine class]]) { 46 | @throw ([NSException tbsm_notAStateMachineException:stateMachine]); 47 | } 48 | stateMachine.parentVertex = self; 49 | [self.priv_parallelStateMachines addObject:stateMachine]; 50 | } 51 | } 52 | 53 | - (void)setStates:(NSArray *> *)states; 54 | { 55 | NSMutableArray *stateMachines = [NSMutableArray new]; 56 | [states enumerateObjectsUsingBlock:^(NSArray<__kindof TBSMState *> *array, NSUInteger idx, BOOL *stop) { 57 | NSString *name = [NSString stringWithFormat:@"%@SubMachine-%lu",self.name, (unsigned long)idx]; 58 | TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:name]; 59 | stateMachine.states = array; 60 | [stateMachines addObject:stateMachine]; 61 | }]; 62 | [self setStateMachines:stateMachines]; 63 | } 64 | 65 | - (void)removeTransitionVertexes 66 | { 67 | [super removeTransitionVertexes]; 68 | [self.priv_parallelStateMachines makeObjectsPerformSelector:@selector(removeTransitionVertexes)]; 69 | } 70 | 71 | - (void)enter:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 72 | { 73 | [super enter:sourceState targetState:targetState data:data]; 74 | 75 | if (self.priv_parallelStateMachines.count == 0) { 76 | @throw [NSException tbsm_missingStateMachineException:self.name]; 77 | } 78 | for (TBSMStateMachine *stateMachine in self.priv_parallelStateMachines) { 79 | if ([targetState.path containsObject:stateMachine]) { 80 | [stateMachine enter:sourceState targetState:targetState data:data]; 81 | } else { 82 | [stateMachine setUp:data]; 83 | } 84 | } 85 | } 86 | 87 | - (void)enter:(TBSMState *)sourceState targetStates:(NSArray *)targetStates region:(TBSMParallelState *)region data:(id)data 88 | { 89 | [super enter:sourceState targetState:region data:data]; 90 | 91 | if (self.priv_parallelStateMachines.count == 0) { 92 | @throw [NSException tbsm_missingStateMachineException:self.name]; 93 | } 94 | for (TBSMStateMachine *stateMachine in self.priv_parallelStateMachines) { 95 | BOOL isEntered = NO; 96 | for (TBSMState *targetState in targetStates) { 97 | if ([targetState.path containsObject:stateMachine]) { 98 | [stateMachine enter:sourceState targetState:targetState data:data]; 99 | isEntered = YES; 100 | } 101 | } 102 | if (!isEntered) { 103 | [stateMachine setUp:data]; 104 | } 105 | } 106 | } 107 | 108 | - (void)exit:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 109 | { 110 | if (self.priv_parallelStateMachines.count == 0) { 111 | @throw [NSException tbsm_missingStateMachineException:self.name]; 112 | } 113 | for (TBSMStateMachine *stateMachine in self.priv_parallelStateMachines) { 114 | [stateMachine tearDown:data]; 115 | } 116 | [super exit:sourceState targetState:targetState data:data]; 117 | } 118 | 119 | - (BOOL)handleEvent:(TBSMEvent *)event 120 | { 121 | BOOL didHandleEvent = NO; 122 | for (TBSMStateMachine *stateMachine in self.priv_parallelStateMachines) { 123 | if ([stateMachine handleEvent:event]) { 124 | didHandleEvent = YES; 125 | } 126 | } 127 | return didHandleEvent; 128 | } 129 | 130 | @end 131 | -------------------------------------------------------------------------------- /Pod/Core/TBSMPseudoState.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMPseudoState.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "NSException+TBStateMachine.h" 11 | #import "TBSMTransitionVertex.h" 12 | #import "TBSMState.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | /** 17 | * This class represents the base class for pseudo states. 18 | */ 19 | @interface TBSMPseudoState : NSObject 20 | 21 | /** 22 | * The name of the pseudo state. 23 | */ 24 | @property (nonatomic, copy, readonly) NSString *name; 25 | 26 | /** 27 | * Initializes a `TBSMPseudoState` with a specified name. 28 | * 29 | * Throws a `TBSMException` when name is nil or an empty string. 30 | * 31 | * @param name The name of the pseudo state. Must be unique. 32 | * 33 | * @return An initialized `TBSMPseudoState` instance. 34 | */ 35 | - (instancetype)initWithName:(NSString *)name; 36 | 37 | /** 38 | * The state this pseudo state leads to inside a compound transition. 39 | * 40 | * @return The target state instance. 41 | */ 42 | - (nonnull TBSMState *)targetState; 43 | 44 | @end 45 | NS_ASSUME_NONNULL_END 46 | -------------------------------------------------------------------------------- /Pod/Core/TBSMPseudoState.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMPseudoState.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMPseudoState.h" 10 | 11 | 12 | @implementation TBSMPseudoState 13 | 14 | - (instancetype)initWithName:(NSString *)name 15 | { 16 | if (name == nil || [name isEqualToString:@""]) { 17 | @throw [NSException tbsm_noNameForPseudoStateException]; 18 | } 19 | self = [super init]; 20 | if (self) { 21 | _name = name.copy; 22 | } 23 | return self; 24 | } 25 | 26 | - (TBSMState *)targetState 27 | { 28 | return nil; 29 | } 30 | @end 31 | -------------------------------------------------------------------------------- /Pod/Core/TBSMState+Notifications.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState+Notifications.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 10.03.18. 6 | // 7 | 8 | #import "TBSMState.h" 9 | 10 | @interface TBSMState (Notifications) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Pod/Core/TBSMState+Notifications.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState+Notifications.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 10.03.18. 6 | // 7 | 8 | #import "TBSMState+Notifications.h" 9 | 10 | @implementation TBSMState (Notifications) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Pod/Core/TBSMState.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBSMHierarchyVertex.h" 12 | #import "TBSMTransitionKind.h" 13 | #import "TBSMTransitionVertex.h" 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | FOUNDATION_EXPORT NSString * const TBSMStateDidEnterNotification; 18 | FOUNDATION_EXPORT NSString * const TBSMStateDidExitNotification; 19 | FOUNDATION_EXPORT NSString * const TBSMDataUserInfo; 20 | 21 | /** 22 | * This type represents a block that is executed on entry and exit of a `TBSMState`. 23 | * 24 | * @param data The payload data. 25 | */ 26 | typedef void (^TBSMStateBlock)(id _Nullable data); 27 | 28 | @class TBSMEventHandler; 29 | 30 | /** 31 | * This class represents a state in a state machine. 32 | */ 33 | @interface TBSMState : NSObject 34 | 35 | /** 36 | * The state's parent vertex inside the state machine hierarchy. 37 | */ 38 | @property (nonatomic, weak) id parentVertex; 39 | 40 | /** 41 | * Block that is executed when the state is entered. 42 | */ 43 | @property (nonatomic, copy, nullable) TBSMStateBlock enterBlock; 44 | 45 | /** 46 | * Block that is executed when the state is exited. 47 | */ 48 | @property (nonatomic, copy, nullable) TBSMStateBlock exitBlock; 49 | 50 | /** 51 | * All `TBSMEventHandler` instances registered to this state instance. 52 | */ 53 | @property (nonatomic, strong, readonly) NSDictionary *> *eventHandlers; 54 | 55 | /** 56 | * Creates a `TBSMState` instance from a given name. 57 | * 58 | * Throws a `TBSMException` when name is nil or an empty string. 59 | * 60 | * @param name The specified state name. 61 | * 62 | * @return The state instance. 63 | */ 64 | + (instancetype)stateWithName:(NSString *)name; 65 | 66 | /** 67 | * Initializes a `TBSMState` with a specified name. 68 | * 69 | * Throws a `TBSMException` when name is nil or an empty string. 70 | * 71 | * @param name The name of the state. Must be unique. 72 | * 73 | * @return An initialized `TBSMState` instance. 74 | */ 75 | - (instancetype)initWithName:(NSString *)name; 76 | 77 | /** 78 | * Registers an event of a given name for transition to a specified target state. 79 | * Defaults to external transition. 80 | * 81 | * @param event The given event name. 82 | * @param target The target vertex. Can be `nil` for internal transitions. 83 | */ 84 | - (void)addHandlerForEvent:(NSString *)event target:(id )target; 85 | 86 | /** 87 | * Registers an event of a given name for transition to a specified target state. 88 | * 89 | * Throws a `TBSMException` if the parameters are ambiguous. 90 | * 91 | * @param event The given event name. 92 | * @param target The target vertex. 93 | * @param kind The kind of transition. 94 | */ 95 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind; 96 | 97 | /** 98 | * Registers an event of a given name for transition to a specified target state. 99 | * 100 | * Throws a `TBSMException` if the parameters are ambiguous. 101 | * 102 | * @param event The given event name. 103 | * @param target The target vertex. 104 | * @param kind The kind of transition. 105 | * @param action The action block associated with this event. 106 | */ 107 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind action:(nullable TBSMActionBlock)action; 108 | 109 | /** 110 | * Registers an event of a given name for transition to a specified target state. 111 | * 112 | * Throws a `TBSMException` if the parameters are ambiguous. 113 | * 114 | * @param event The given event name. 115 | * @param target The target vertex. 116 | * @param kind The kind of transition. 117 | * @param action The action block associated with this event. 118 | * @param guard The guard block associated with this event. 119 | */ 120 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind action:(nullable TBSMActionBlock)action guard:(nullable TBSMGuardBlock)guard; 121 | 122 | /** 123 | * Returns `YES` if a given event can be consumed by the state. 124 | * 125 | * @param event The event to check. 126 | * 127 | * @return `YES` if the event can be consumed. 128 | */ 129 | - (BOOL)hasHandlerForEvent:(TBSMEvent *)event; 130 | 131 | /** 132 | * Returns an array of `TBSMEventHandler` instances for a given event. 133 | * 134 | * @param event The event to handle 135 | * 136 | * @return The array containing the corresponding event handlers. 137 | */ 138 | - (nullable NSArray *)eventHandlersForEvent:(TBSMEvent *)event; 139 | 140 | @end 141 | NS_ASSUME_NONNULL_END 142 | -------------------------------------------------------------------------------- /Pod/Core/TBSMState.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMState.h" 10 | #import "NSException+TBStateMachine.h" 11 | #import "TBSMEventHandler.h" 12 | 13 | NSString * const TBSMStateDidEnterNotification = @"TBSMStateDidEnterNotification"; 14 | NSString * const TBSMStateDidExitNotification = @"TBSMStateDidExitNotification"; 15 | NSString * const TBSMDataUserInfo = @"data"; 16 | 17 | @interface TBSMState () 18 | @property (nonatomic, copy) NSString *name; 19 | @property (nonatomic, strong) NSMutableDictionary *priv_eventHandlers; 20 | @end 21 | 22 | @implementation TBSMState 23 | 24 | + (instancetype)stateWithName:(NSString *)name 25 | { 26 | return [[[self class] alloc] initWithName:name]; 27 | } 28 | 29 | - (instancetype)initWithName:(NSString *)name 30 | { 31 | if (name == nil || [name isEqualToString:@""]) { 32 | @throw [NSException tbsm_noNameForStateException]; 33 | } 34 | self = [super init]; 35 | if (self) { 36 | _name = name.copy; 37 | _priv_eventHandlers = [NSMutableDictionary new]; 38 | } 39 | return self; 40 | } 41 | 42 | - (void)removeTransitionVertexes 43 | { 44 | [self.priv_eventHandlers removeAllObjects]; 45 | self.priv_eventHandlers = nil; 46 | } 47 | 48 | - (NSDictionary *)eventHandlers 49 | { 50 | return self.priv_eventHandlers.copy; 51 | } 52 | 53 | - (void)addHandlerForEvent:(NSString *)event target:(id )target 54 | { 55 | [self addHandlerForEvent:event target:target kind:TBSMTransitionExternal]; 56 | } 57 | 58 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind 59 | { 60 | [self addHandlerForEvent:event target:target kind:kind action:nil guard:nil]; 61 | } 62 | 63 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind action:(TBSMActionBlock)action 64 | { 65 | [self addHandlerForEvent:event target:target kind:kind action:action guard:nil]; 66 | } 67 | 68 | - (void)addHandlerForEvent:(NSString *)event target:(id )target kind:(TBSMTransitionKind)kind action:(TBSMActionBlock)action guard:(TBSMGuardBlock)guard 69 | { 70 | if (target == nil) { 71 | @throw [NSException tbsm_ambiguousTransitionAttributes:event source:self.name target:target.name]; 72 | } 73 | if (kind == TBSMTransitionInternal && target != self) { 74 | @throw [NSException tbsm_ambiguousTransitionAttributes:event source:self.name target:target.name]; 75 | } 76 | TBSMEventHandler *eventHandler = [[TBSMEventHandler alloc] initWithName:event target:target kind:kind action:action guard:guard]; 77 | if (!self.priv_eventHandlers[event]) { 78 | self.priv_eventHandlers[event] = NSMutableArray.new; 79 | } 80 | [self.priv_eventHandlers[event] addObject:eventHandler]; 81 | } 82 | 83 | - (BOOL)hasHandlerForEvent:(TBSMEvent *)event 84 | { 85 | return (self.priv_eventHandlers[event.name] != nil); 86 | } 87 | 88 | - (NSArray *)eventHandlersForEvent:(TBSMEvent *)event 89 | { 90 | if ([self hasHandlerForEvent:event]) { 91 | return self.priv_eventHandlers[event.name]; 92 | } 93 | return nil; 94 | } 95 | 96 | - (void)enter:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 97 | { 98 | [self tbsm_postNotificationWithName:TBSMStateDidEnterNotification data:data]; 99 | 100 | if (_enterBlock) { 101 | _enterBlock(data); 102 | } 103 | } 104 | 105 | - (void)exit:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 106 | { 107 | [self tbsm_postNotificationWithName:TBSMStateDidExitNotification data:data]; 108 | 109 | if (_exitBlock) { 110 | _exitBlock(data); 111 | } 112 | } 113 | 114 | - (void)tbsm_postNotificationWithName:(NSString *)name data:(id)data 115 | { 116 | NSMutableDictionary *userInfo = NSMutableDictionary.new; 117 | if (data) { 118 | userInfo[TBSMDataUserInfo] = data; 119 | } 120 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:self userInfo:userInfo]; 121 | } 122 | 123 | #pragma mark - TBSMHierarchyVertex 124 | 125 | - (NSArray *)path 126 | { 127 | NSMutableArray *path = [NSMutableArray new]; 128 | id state = self; 129 | while (state) { 130 | [path insertObject:state atIndex:0]; 131 | state = state.parentVertex; 132 | } 133 | return path; 134 | } 135 | 136 | @end 137 | -------------------------------------------------------------------------------- /Pod/Core/TBSMStateMachine+Notifications.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine+Notifications.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 10.03.18. 6 | // 7 | 8 | #import "TBSMStateMachine.h" 9 | 10 | @interface TBSMStateMachine (Notifications) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Pod/Core/TBSMStateMachine+Notifications.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine+Notifications.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 10.03.18. 6 | // 7 | 8 | #import "TBSMStateMachine+Notifications.h" 9 | 10 | @implementation TBSMStateMachine (Notifications) 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /Pod/Core/TBSMStateMachine.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBSMState.h" 12 | #import "TBSMTransition.h" 13 | #import "TBSMCompoundTransition.h" 14 | #import "TBSMEvent.h" 15 | #import "TBSMEventHandler.h" 16 | #import "TBSMParallelState.h" 17 | #import "TBSMSubState.h" 18 | #import "TBSMFork.h" 19 | #import "TBSMJoin.h" 20 | #import "TBSMJunction.h" 21 | #import "TBSMMacros.h" 22 | #import "NSException+TBStateMachine.h" 23 | 24 | NS_ASSUME_NONNULL_BEGIN 25 | 26 | /** 27 | * This class represents a hierarchical state machine. 28 | */ 29 | @interface TBSMStateMachine : NSObject 30 | 31 | /** 32 | * The operation queue to handle the run to completion steps. 33 | * Should be serial. 34 | * 35 | * Throws a `TBSMException` when trying to set a queue which is not serial. 36 | */ 37 | @property (nonatomic, strong) NSOperationQueue *scheduledEventsQueue; 38 | 39 | /** 40 | * The state the state machine wil enter on setup (by default the first state in the provided array will be set). 41 | * 42 | * Throws a `TBSMException` if the state does not exist in the statemachine. 43 | */ 44 | @property (nonatomic, strong) TBSMState *initialState; 45 | 46 | /** 47 | * The current state the state machine resides in. Set to be nil before -setUp: and after -tearDown: being called. 48 | */ 49 | @property (nonatomic, strong, readonly, nullable) TBSMState *currentState; 50 | 51 | /** 52 | * Creates a `TBSMStateMachine` instance from a given name. 53 | * 54 | * Throws a `TBSMException` when name is nil or an empty string. 55 | * 56 | * @param name The specified state machine name. 57 | * 58 | * @return The state machine instance. 59 | */ 60 | + (instancetype)stateMachineWithName:(NSString *)name; 61 | 62 | /** 63 | * Initializes a `TBSMStateMachine` with a specified name. 64 | * 65 | * Throws a `TBSMException` when name is nil or an empty string. 66 | * 67 | * @param name The name of the state machine. Must be unique. 68 | * 69 | * @return An initialized `TBSMStateMachine` instance. 70 | */ 71 | - (instancetype)initWithName:(NSString *)name; 72 | 73 | /** 74 | * Starts up the state machine. Will enter the initial state. 75 | * 76 | * Throws `TBSMException` if initial state has not been set beforehand. 77 | */ 78 | - (void)setUp:(nullable id)data; 79 | 80 | /** 81 | * Leaves the current state and shuts down the state machine. 82 | */ 83 | - (void)tearDown:(nullable id)data; 84 | 85 | /** 86 | * Returns all states inside the state machine. 87 | * 88 | * @return An NSArray containing all `TBSMState` instances. 89 | */ 90 | - (NSArray<__kindof TBSMState *> *)states; 91 | 92 | /** 93 | * Sets all states the state machine will manage. First state in array wil be set as initialState. 94 | * 95 | * Throws `TBSMException` if states are not of type `TBSMState`. 96 | * 97 | * @param states An `NSArray` containing all state objects. 98 | */ 99 | - (void)setStates:(NSArray<__kindof TBSMState *> *)states; 100 | 101 | /** 102 | * Adds an event to the event queue. 103 | * 104 | * @param event The given `TBSMEvent` instance. 105 | */ 106 | - (void)scheduleEvent:(TBSMEvent *)event; 107 | 108 | /** 109 | * Adds an event to the event queue. Convenience method which receives the event name and payload. 110 | * 111 | * @param name The specified event name. 112 | * @param data Optional payload data. 113 | */ 114 | - (void)scheduleEventNamed:(NSString *)name data:(nullable id)data; 115 | 116 | /** 117 | * Switches between states defined in a specified transition. 118 | * 119 | * @param sourceState The source state. 120 | * @param targetState The target state. 121 | * @param action The action to execute. 122 | * @param data The payload data. 123 | */ 124 | - (void)switchState:(nullable TBSMState *)sourceState targetState:(nullable TBSMState *)targetState action:(nullable TBSMActionBlock)action data:(nullable id)data; 125 | 126 | /** 127 | * Switches between states defined in a specified transition. 128 | * 129 | * @param sourceState The source state. 130 | * @param targetStates The target states inside the specified region. 131 | * @param region The target region. 132 | * @param action The action to execute. 133 | * @param data The payload data. 134 | */ 135 | - (void)switchState:(nullable TBSMState *)sourceState targetStates:(NSArray<__kindof TBSMState *> *)targetStates region:(TBSMParallelState *)region action:(nullable TBSMActionBlock)action data:(nullable id)data; 136 | 137 | /** 138 | * Returns the state at the specified path. 139 | * 140 | * Throws `TBSMException` if state could not be found. 141 | * 142 | * @param path The specified path 143 | * 144 | * @return The specified state. 145 | */ 146 | - (TBSMState *)stateWithPath:(NSString *)path; 147 | 148 | /** 149 | * Subscribe to a `TBSMStateDidEnterNotification` of the state at the specified path. 150 | * 151 | * Throws `TBSMException` if state could not be found. 152 | * 153 | * @param path The path of the state to subscribe to. 154 | * @param observer The observer to notify. 155 | * @param selector The selector to execute when receiving the notification. 156 | */ 157 | - (void)subscribeToEntryAtPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector; 158 | 159 | /** 160 | * Subscribe to a `TBSMStateDidExitNotification` of the state at the specified path. 161 | * 162 | * Throws `TBSMException` if state could not be found. 163 | * 164 | * @param path The path of the state to subscribe to. 165 | * @param observer The observer to notify. 166 | * @param selector The selector to execute when receiving the notification. 167 | */ 168 | - (void)subscribeToExitAtPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector; 169 | 170 | /** 171 | * Subscribe to an action of an internal transition of the state at the specified path. 172 | * 173 | * Throws `TBSMException` if state could not be found. 174 | * 175 | * @param action The name of the internal transition. 176 | * @param path The path of the state to subscribe to. 177 | * @param observer The observer to notify. 178 | * @param selector The selector to execute when receiving the notification. 179 | */ 180 | - (void)subscribeToAction:(NSString *)action atPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector; 181 | 182 | /** 183 | * Unsubscribe from a `TBSMStateDidEnterNotification` of the state at the specified path. 184 | * 185 | * Throws `TBSMException` if state could not be found. 186 | * 187 | * @param path The path of the state to unsubscribe from. 188 | * @param observer The observer to notify. 189 | */ 190 | - (void)unsubscribeFromEntryAtPath:(NSString *)path forObserver:(NSObject *)observer; 191 | 192 | /** 193 | * Unsubscribe from a `TBSMStateDidExitNotification` of the state at the specified path. 194 | * 195 | * Throws `TBSMException` if state could not be found. 196 | * 197 | * @param path The path of the state to unsubscribe from. 198 | * @param observer The observer to notify. 199 | */ 200 | - (void)unsubscribeFromExitAtPath:(NSString *)path forObserver:(NSObject *)observer; 201 | 202 | /** 203 | * Unsubscribe from an action of an internal transition of the state at the specified path. 204 | * 205 | * Throws `TBSMException` if state could not be found. 206 | * 207 | * @param action The name of the internal transition. 208 | * @param path The path of the state to unsubscribe from. 209 | * @param observer The observer to notify. 210 | */ 211 | - (void)unsubscribeFromAction:(NSString *)action atPath:(NSString *)path forObserver:(NSObject *)observer; 212 | 213 | @end 214 | NS_ASSUME_NONNULL_END 215 | -------------------------------------------------------------------------------- /Pod/Core/TBSMStateMachine.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMStateMachine.h" 10 | 11 | @interface TBSMStateMachine () 12 | @property (nonatomic, copy, readonly) NSString *name; 13 | @property (nonatomic, weak) id parentVertex; 14 | @property (nonatomic, strong) NSMutableArray *priv_states; 15 | @end 16 | 17 | @implementation TBSMStateMachine 18 | 19 | + (instancetype)stateMachineWithName:(NSString *)name 20 | { 21 | return [[[self class] alloc] initWithName:name]; 22 | } 23 | 24 | - (instancetype)initWithName:(NSString *)name 25 | { 26 | if (name == nil || [name isEqualToString:@""]) { 27 | @throw [NSException tbsm_noNameForStateException]; 28 | } 29 | self = [super init]; 30 | if (self) { 31 | _name = name.copy; 32 | _priv_states = [NSMutableArray new]; 33 | _scheduledEventsQueue = [NSOperationQueue mainQueue]; 34 | } 35 | return self; 36 | } 37 | 38 | - (void)removeTransitionVertexes 39 | { 40 | [self.priv_states makeObjectsPerformSelector:@selector(removeTransitionVertexes)]; 41 | [self.priv_states removeAllObjects]; 42 | } 43 | 44 | - (void)dealloc 45 | { 46 | [self removeTransitionVertexes]; 47 | } 48 | 49 | - (NSArray *)states 50 | { 51 | return self.priv_states.copy; 52 | } 53 | 54 | - (void)setStates:(NSArray *)states 55 | { 56 | [self.priv_states removeAllObjects]; 57 | 58 | for (id object in states) { 59 | if (![object isKindOfClass:[TBSMState class]]) { 60 | @throw ([NSException tbsm_notOfTypeStateException:object]); 61 | } 62 | TBSMState *state = object; 63 | [state setParentVertex:self]; 64 | [self.priv_states addObject:state]; 65 | } 66 | if (states.count > 0) { 67 | _initialState = states[0]; 68 | } 69 | } 70 | 71 | - (void)setInitialState:(TBSMState *)initialState 72 | { 73 | if (![self.priv_states containsObject:initialState]) { 74 | @throw [NSException tbsm_nonExistingStateException:initialState.name]; 75 | } 76 | _initialState = initialState; 77 | } 78 | 79 | - (void)setScheduledEventsQueue:(NSOperationQueue *)scheduledEventsQueue 80 | { 81 | if (scheduledEventsQueue.maxConcurrentOperationCount > 1) { 82 | @throw [NSException tbsm_noSerialQueueException:scheduledEventsQueue.name]; 83 | } 84 | _scheduledEventsQueue = scheduledEventsQueue; 85 | } 86 | 87 | - (void)setUp:(id)data 88 | { 89 | if (!self.initialState) { 90 | @throw [NSException tbsm_noInitialStateException:self.name]; 91 | } 92 | [self enter:nil targetState:self.initialState data:data]; 93 | } 94 | 95 | - (void)tearDown:(id)data 96 | { 97 | [self.scheduledEventsQueue cancelAllOperations]; 98 | [self exit:self.currentState targetState:nil data:data]; 99 | _currentState = nil; 100 | } 101 | 102 | #pragma mark - handling events 103 | 104 | - (void)scheduleEvent:(TBSMEvent *)event 105 | { 106 | if (self.parentVertex) { 107 | TBSMStateMachine *topStateMachine = (TBSMStateMachine *)[self.parentVertex parentVertex]; 108 | [topStateMachine scheduleEvent:event]; 109 | return; 110 | } 111 | 112 | [self.scheduledEventsQueue addOperationWithBlock:^{ 113 | [self handleEvent:event]; 114 | }]; 115 | } 116 | 117 | - (void)scheduleEventNamed:(NSString *)name data:(id)data 118 | { 119 | [self scheduleEvent:[TBSMEvent eventWithName:name data:data]]; 120 | } 121 | 122 | - (BOOL)handleEvent:(TBSMEvent *)event 123 | { 124 | if (self.currentState == nil) { 125 | return NO; 126 | } 127 | 128 | if ([self.currentState respondsToSelector:@selector(handleEvent:)]) { 129 | if ([self.currentState performSelector:@selector(handleEvent:) withObject:event]) { 130 | return YES; 131 | } 132 | } 133 | NSArray *eventHandlers = [self.currentState eventHandlersForEvent:event]; 134 | for (TBSMEventHandler *eventHandler in eventHandlers) { 135 | 136 | TBSMTransition *transition = nil; 137 | if ([eventHandler.target isKindOfClass:[TBSMState class]]) { 138 | transition = [[TBSMTransition alloc] initWithSourceState:self.currentState 139 | targetState:(TBSMState *)eventHandler.target 140 | kind:eventHandler.kind 141 | action:eventHandler.action 142 | guard:eventHandler.guard 143 | eventName:event.name]; 144 | } else { 145 | transition = [[TBSMCompoundTransition alloc] initWithSourceState:self.currentState 146 | targetPseudoState:(TBSMPseudoState *)eventHandler.target 147 | action:eventHandler.action 148 | guard:eventHandler.guard 149 | eventName:event.name]; 150 | } 151 | if ([transition performTransitionWithData:event.data]) { 152 | return YES; 153 | } 154 | } 155 | return NO; 156 | } 157 | 158 | #pragma mark - State switching 159 | 160 | - (void)switchState:(TBSMState *)sourceState targetState:(TBSMState *)targetState action:(TBSMActionBlock)action data:(id)data 161 | { 162 | [self.currentState exit:sourceState targetState:targetState data:data]; 163 | if (action) { 164 | action(data); 165 | } 166 | [self enter:sourceState targetState:targetState data:data]; 167 | } 168 | 169 | - (void)switchState:(TBSMState *)sourceState targetStates:(NSArray *)targetStates region:(TBSMParallelState *)region action:(TBSMActionBlock)action data:(id)data 170 | { 171 | [self.currentState exit:sourceState targetState:region data:data]; 172 | if (action) { 173 | action(data); 174 | } 175 | [self enter:sourceState targetStates:targetStates region:region data:data]; 176 | } 177 | 178 | - (void)enter:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 179 | { 180 | NSUInteger targetLevel = targetState.parentVertex.path.count; 181 | NSUInteger thisLevel = self.path.count; 182 | 183 | if (targetLevel < thisLevel) { 184 | _currentState = self.initialState; 185 | } else if (targetLevel == thisLevel) { 186 | _currentState = targetState; 187 | } else { 188 | NSArray *targetPath = [targetState.parentVertex path]; 189 | id vertex = targetPath[thisLevel]; 190 | _currentState = (TBSMState *)vertex.parentVertex; 191 | } 192 | [self.currentState enter:sourceState targetState:targetState data:data]; 193 | } 194 | 195 | - (void)enter:(TBSMState *)sourceState targetStates:(NSArray *)targetStates region:(TBSMParallelState *)region data:(id)data 196 | { 197 | NSUInteger targetLevel = [[region.parentVertex path] count]; 198 | NSUInteger thisLevel = self.path.count; 199 | 200 | if (targetLevel == thisLevel) { 201 | _currentState = region; 202 | } else if (targetLevel > thisLevel) { 203 | NSArray *targetPath = [region.parentVertex path]; 204 | id vertex = targetPath[thisLevel]; 205 | _currentState = (TBSMState *)vertex.parentVertex; 206 | } 207 | id vertex = (id )_currentState; 208 | [vertex enter:sourceState targetStates:targetStates region:region data:data]; 209 | } 210 | 211 | - (void)exit:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 212 | { 213 | [self.currentState exit:sourceState targetState:targetState data:data]; 214 | } 215 | 216 | - (void)subscribeToEntryAtPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector 217 | { 218 | TBSMState *state = [self stateWithPath:path]; 219 | [[NSNotificationCenter defaultCenter] addObserver:observer selector:selector name:TBSMStateDidEnterNotification object:state]; 220 | } 221 | 222 | - (void)subscribeToExitAtPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector 223 | { 224 | TBSMState *state = [self stateWithPath:path]; 225 | [[NSNotificationCenter defaultCenter] addObserver:observer selector:selector name:TBSMStateDidExitNotification object:state]; 226 | } 227 | 228 | - (void)subscribeToAction:(NSString *)action atPath:(NSString *)path forObserver:(NSObject *)observer selector:(nonnull SEL)selector 229 | { 230 | TBSMState *state = [self stateWithPath:path]; 231 | [[NSNotificationCenter defaultCenter] addObserver:observer selector:selector name:action object:state]; 232 | } 233 | 234 | - (void)unsubscribeFromEntryAtPath:(NSString *)path forObserver:(NSObject *)observer 235 | { 236 | TBSMState *state = [self stateWithPath:path]; 237 | [[NSNotificationCenter defaultCenter] removeObserver:observer name:TBSMStateDidEnterNotification object:state]; 238 | } 239 | 240 | - (void)unsubscribeFromExitAtPath:(NSString *)path forObserver:(NSObject *)observer 241 | { 242 | TBSMState *state = [self stateWithPath:path]; 243 | [[NSNotificationCenter defaultCenter] removeObserver:observer name:TBSMStateDidExitNotification object:state]; 244 | } 245 | 246 | - (void)unsubscribeFromAction:(NSString *)action atPath:(NSString *)path forObserver:(NSObject *)observer 247 | { 248 | TBSMState *state = [self stateWithPath:path]; 249 | [[NSNotificationCenter defaultCenter] removeObserver:observer name:action object:state]; 250 | } 251 | 252 | - (TBSMState *)_stateWithName:(NSString *)name 253 | { 254 | for (TBSMState *state in self.states) { 255 | if ([state.name isEqualToString:name]) { 256 | return state; 257 | } 258 | } 259 | return nil; 260 | } 261 | 262 | - (TBSMState *)stateWithPath:(NSString *)path 263 | { 264 | TBSMStateMachine *statemachine = self; 265 | TBSMState *state; 266 | 267 | NSArray *components = [path componentsSeparatedByString:@"/"]; 268 | for (NSString *component in components) { 269 | NSArray *elements = [component componentsSeparatedByString:@"@"]; 270 | NSString *name = elements.firstObject; 271 | NSString *region = elements.lastObject; 272 | 273 | state = [statemachine _stateWithName:name]; 274 | 275 | if ([state isKindOfClass:TBSMSubState.class]) { 276 | TBSMSubState *sub = (TBSMSubState *)state; 277 | statemachine = sub.stateMachine; 278 | } 279 | if ([state isKindOfClass:TBSMParallelState.class]) { 280 | if (region == nil) { 281 | @throw [NSException tbsm_invalidPath:path]; 282 | } 283 | TBSMParallelState *par = (TBSMParallelState *)state; 284 | NSInteger index = region.integerValue; 285 | if (index < 0 || index >= par.stateMachines.count) { 286 | @throw [NSException tbsm_invalidPath:path]; 287 | } 288 | statemachine = par.stateMachines[index]; 289 | } 290 | } 291 | if (state == nil) { 292 | @throw [NSException tbsm_invalidPath:path]; 293 | } 294 | return state; 295 | } 296 | 297 | #pragma mark - TBSMHierarchyVertex 298 | 299 | - (NSArray *)path 300 | { 301 | NSMutableArray *path = [NSMutableArray new]; 302 | TBSMStateMachine *stateMachine = self; 303 | while (stateMachine) { 304 | [path insertObject:stateMachine atIndex:0]; 305 | stateMachine = (TBSMStateMachine *)stateMachine.parentVertex.parentVertex; 306 | } 307 | return path; 308 | } 309 | 310 | @end 311 | -------------------------------------------------------------------------------- /Pod/Core/TBSMSubState.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMSubState.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 19.09.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "TBSMState.h" 11 | #import "TBSMContainingVertex.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class TBSMStateMachine; 16 | 17 | /** 18 | * This class allows the create nested states. 19 | */ 20 | @interface TBSMSubState : TBSMState 21 | 22 | /** 23 | * The `TBSMStateMachine` instance contained in this sub state. 24 | */ 25 | @property (nonatomic, strong) TBSMStateMachine *stateMachine; 26 | 27 | /** 28 | * Creates a `TBSMSUBState` with a specified name. 29 | * 30 | * Throws a `TBSMException` when name is nil or an empty string. 31 | * 32 | * @param name The name of this wrapper. Must be unique. 33 | * 34 | * @return A new `TBSMSubState` instance. 35 | */ 36 | + (instancetype)subStateWithName:(NSString *)name; 37 | 38 | /** 39 | * Sets all states the sub state will manage. First state in array wil be set as initialState. 40 | * Creates a state machine implicitly. 41 | * 42 | * Throws `TBSMException` if states are not of type `TBSMState`. 43 | * 44 | * @param states An `NSArray` containing all state objects. 45 | */ 46 | - (void)setStates:(NSArray<__kindof TBSMState *> *)states; 47 | 48 | @end 49 | NS_ASSUME_NONNULL_END 50 | -------------------------------------------------------------------------------- /Pod/Core/TBSMSubState.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMSubState.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 19.09.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMSubState.h" 10 | #import "TBSMStateMachine.h" 11 | #import "NSException+TBStateMachine.h" 12 | 13 | 14 | @implementation TBSMSubState 15 | 16 | + (instancetype)subStateWithName:(NSString *)name 17 | { 18 | return [[[self class] alloc] initWithName:name]; 19 | } 20 | 21 | - (void)setStateMachine:(TBSMStateMachine *)stateMachine 22 | { 23 | if (![stateMachine isKindOfClass:[TBSMStateMachine class]]) { 24 | @throw ([NSException tbsm_notAStateMachineException:stateMachine]); 25 | } 26 | _stateMachine = stateMachine; 27 | [_stateMachine setParentVertex:self]; 28 | } 29 | 30 | - (void)setStates:(NSArray<__kindof TBSMState *> *)states 31 | { 32 | NSString *name = [self.name stringByAppendingString:@"SubMachine"]; 33 | TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:name]; 34 | stateMachine.states = states; 35 | [self setStateMachine:stateMachine]; 36 | } 37 | 38 | - (void)removeTransitionVertexes 39 | { 40 | [super removeTransitionVertexes]; 41 | [self.stateMachine removeTransitionVertexes]; 42 | } 43 | 44 | - (void)enter:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 45 | { 46 | if (self.stateMachine == nil) { 47 | @throw [NSException tbsm_missingStateMachineException:self.name]; 48 | } 49 | [super enter:sourceState targetState:targetState data:data]; 50 | [_stateMachine enter:sourceState targetState:targetState data:data]; 51 | } 52 | 53 | - (void)enter:(TBSMState *)sourceState targetStates:(NSArray *)targetStates region:(TBSMParallelState *)region data:(id)data 54 | { 55 | if (self.stateMachine == nil) { 56 | @throw [NSException tbsm_missingStateMachineException:self.name]; 57 | } 58 | [super enter:sourceState targetState:region data:data]; 59 | [_stateMachine enter:sourceState targetStates:targetStates region:region data:data]; 60 | } 61 | 62 | - (void)exit:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 63 | { 64 | if (self.stateMachine == nil) { 65 | @throw [NSException tbsm_missingStateMachineException:self.name]; 66 | } 67 | [_stateMachine tearDown:data]; 68 | [super exit:sourceState targetState:targetState data:data]; 69 | } 70 | 71 | - (BOOL)handleEvent:(TBSMEvent *)event 72 | { 73 | return [_stateMachine handleEvent:event]; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /Pod/Core/TBSMTransition.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransition.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBSMTransitionKind.h" 12 | 13 | NS_ASSUME_NONNULL_BEGIN 14 | 15 | @class TBSMState; 16 | @class TBSMStateMachine; 17 | 18 | /** 19 | * This type represents an action of a `TBSMTransition`. 20 | * 21 | * @param data The payload data. 22 | */ 23 | typedef void(^TBSMActionBlock)(id _Nullable data); 24 | 25 | /** 26 | * This type represents a guard function of a `TBSMTransition`. 27 | * 28 | * @param data The payload data. 29 | */ 30 | typedef BOOL(^TBSMGuardBlock)(id _Nullable data); 31 | 32 | 33 | /** 34 | * This class represents a transition between two states. 35 | */ 36 | @interface TBSMTransition : NSObject 37 | 38 | /** 39 | * The source state. 40 | */ 41 | @property (nonatomic, weak) TBSMState *sourceState; 42 | 43 | /** 44 | * The target state. 45 | */ 46 | @property (nonatomic, weak) TBSMState *targetState; 47 | 48 | /** 49 | * The kind of transition. 50 | */ 51 | @property (nonatomic, assign) TBSMTransitionKind kind; 52 | 53 | /** 54 | * The action associated with the transition. 55 | */ 56 | @property (nonatomic, copy, nullable) TBSMActionBlock action; 57 | 58 | /** 59 | * The guard function associated with the transition. 60 | */ 61 | @property (nonatomic, copy, nullable) TBSMGuardBlock guard; 62 | 63 | /** 64 | * The name of the event being handled. 65 | * 66 | */ 67 | @property (nonatomic, strong) NSString *eventName; 68 | 69 | /** 70 | * Initializes a `TBSMTransition` instance from a given source and target state, action and guard. 71 | * 72 | * @param sourceState The specified source state. 73 | * @param targetState The specified target state. 74 | * @param kind The kind of transition. 75 | * @param action The action associated with this transition. 76 | * @param guard The guard function associated with the transition. 77 | * @param eventName The name of the event being handled. 78 | * 79 | * @return The transition object. 80 | */ 81 | - (instancetype)initWithSourceState:(TBSMState *)sourceState 82 | targetState:(nullable TBSMState *)targetState 83 | kind:(TBSMTransitionKind)kind 84 | action:(nullable TBSMActionBlock)action 85 | guard:(nullable TBSMGuardBlock)guard 86 | eventName:(NSString *)eventName; 87 | 88 | 89 | - (TBSMStateMachine *)findLeastCommonAncestor; 90 | 91 | /** 92 | * Checks wether the transition can be performed by evaluating its guards. 93 | * 94 | * @param data The payload data. 95 | * 96 | * @return Returns YES if the transition can be performed. 97 | */ 98 | - (BOOL)canPerformTransitionWithData:(id)data; 99 | 100 | /** 101 | * Performs the transition between source and target state. 102 | * Evaluates guard and action blocks. 103 | * Determines the lca considering the transiton kind. 104 | * 105 | * Throws a `TBSMException` if transition could not be resolved. 106 | * 107 | * @param data The payload data. 108 | * 109 | * @return Returns YES if the transition can be performed. 110 | */ 111 | - (BOOL)performTransitionWithData:(nullable id)data; 112 | 113 | /** 114 | * The transition's name. 115 | * 116 | * @return The name. 117 | */ 118 | - (NSString *)name; 119 | 120 | @end 121 | 122 | NS_ASSUME_NONNULL_END 123 | -------------------------------------------------------------------------------- /Pod/Core/TBSMTransition.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransition.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.06.14. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMTransition.h" 10 | #import "TBSMState.h" 11 | #import "TBSMStateMachine.h" 12 | 13 | @implementation TBSMTransition 14 | 15 | - (instancetype)initWithSourceState:(TBSMState *)sourceState 16 | targetState:(TBSMState *)targetState 17 | kind:(TBSMTransitionKind)kind 18 | action:(TBSMActionBlock)action 19 | guard:(TBSMGuardBlock)guard 20 | eventName:(NSString *)eventName 21 | { 22 | self = [super init]; 23 | if (self) { 24 | self.sourceState = sourceState; 25 | self.targetState = targetState; 26 | self.kind = kind; 27 | self.action = action; 28 | self.guard = guard; 29 | self.eventName = eventName; 30 | } 31 | return self; 32 | } 33 | 34 | - (NSString *)name 35 | { 36 | if (self.targetState == nil) { 37 | return self.sourceState.name; 38 | } 39 | return [NSString stringWithFormat:@"%@ --> %@", self.sourceState.name, self.targetState.name]; 40 | } 41 | 42 | - (TBSMStateMachine *)findLeastCommonAncestor 43 | { 44 | NSArray *sourcePath = [self.sourceState path]; 45 | NSArray *targetPath = [self.targetState path]; 46 | 47 | __block TBSMStateMachine *lca = nil; 48 | [sourcePath enumerateObjectsWithOptions:NSEnumerationReverse usingBlock:^(id obj, NSUInteger idx, BOOL *stop) { 49 | if ([obj isKindOfClass:[TBSMStateMachine class]]) { 50 | if ([targetPath containsObject:obj]) { 51 | lca = (TBSMStateMachine *)obj; 52 | *stop = YES; 53 | } 54 | } 55 | }]; 56 | 57 | if (self.kind == TBSMTransitionLocal) { 58 | if ([self.sourceState.path containsObject:self.targetState] || [self.targetState.path containsObject:self.sourceState]) { 59 | TBSMSubState *containingSubState = (TBSMSubState *)lca.currentState; 60 | lca = containingSubState.stateMachine; 61 | } 62 | } 63 | if (!lca) { 64 | @throw [NSException tbsm_noLcaForTransition:self.name]; 65 | } 66 | return lca; 67 | } 68 | 69 | - (BOOL)canPerformTransitionWithData:(id)data 70 | { 71 | return (self.guard == nil || self.guard(data)); 72 | } 73 | 74 | - (BOOL)performTransitionWithData:(id)data 75 | { 76 | if ([self canPerformTransitionWithData:data] == NO) { 77 | return NO; 78 | } 79 | if (self.kind == TBSMTransitionInternal) { 80 | if (self.action) { 81 | self.action(data); 82 | } 83 | [self _postInternalTransitionActionNotificationWithData:data]; 84 | } else { 85 | TBSMStateMachine *lca = [self findLeastCommonAncestor]; 86 | [lca switchState:self.sourceState targetState:self.targetState action:self.action data:data]; 87 | } 88 | return YES; 89 | } 90 | 91 | - (void)_postInternalTransitionActionNotificationWithData:(id)data 92 | { 93 | NSMutableDictionary *userInfo = NSMutableDictionary.new; 94 | if (data) { 95 | userInfo[TBSMDataUserInfo] = data; 96 | } 97 | [[NSNotificationCenter defaultCenter] postNotificationName:self.eventName object:self.targetState userInfo:userInfo]; 98 | } 99 | 100 | @end 101 | -------------------------------------------------------------------------------- /Pod/Core/TBSMTransitionKind.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransitionKind.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 03.02.15. 6 | // Copyright (c) 2015 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #ifndef Pods_TBSMTransitionKind_h 10 | #define Pods_TBSMTransitionKind_h 11 | 12 | /** 13 | * This enum defines the transition types that can be defined. 14 | */ 15 | typedef NS_ENUM(NSUInteger, TBSMTransitionKind) { 16 | TBSMTransitionExternal, 17 | TBSMTransitionLocal, 18 | TBSMTransitionInternal 19 | }; 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /Pod/Core/TBSMTransitionVertex.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransitionVertex.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 21.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | /** 14 | * This protocol represents a vertex inside a compound transition. 15 | * 16 | * Classes which implement this protocol can be used to construct compound transitions. 17 | */ 18 | @protocol TBSMTransitionVertex 19 | 20 | /** 21 | * Returns the vertex' name. 22 | * 23 | * Classes which implement this method must return a unique name. 24 | * 25 | * @return The name as a string. 26 | */ 27 | - (NSString *)name; 28 | 29 | @end 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugLogger.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugLogger.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.10.16. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | @interface TBSMDebugLogger : NSObject 13 | 14 | + (instancetype)sharedInstance; 15 | - (void)log:(NSString *)format, ...; 16 | @end 17 | NS_ASSUME_NONNULL_END 18 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugLogger.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugLogger.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 16.10.16. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMDebugLogger.h" 10 | 11 | @interface TBSMDebugLogger () 12 | 13 | @property (nonatomic, strong) dispatch_queue_t loggingQueue; 14 | @end 15 | 16 | @implementation TBSMDebugLogger 17 | 18 | + (instancetype)sharedInstance 19 | { 20 | static TBSMDebugLogger *_debugLogger = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | _debugLogger = [TBSMDebugLogger new]; 24 | }); 25 | return _debugLogger; 26 | } 27 | 28 | - (instancetype)init 29 | { 30 | self = [super init]; 31 | if (self) { 32 | _loggingQueue = dispatch_queue_create("jkrumow.TBStateMachine.DebugLogger.loggingQueue", DISPATCH_QUEUE_SERIAL); 33 | } 34 | return self; 35 | } 36 | 37 | - (void)log:(NSString *)format, ... 38 | { 39 | NSString *logMessage; 40 | va_list args; 41 | va_start(args, format); 42 | logMessage = [[NSString alloc] initWithFormat:format arguments:args]; 43 | va_end(args); 44 | 45 | dispatch_async(self.loggingQueue, ^{ 46 | NSLog(@"%@", logMessage); 47 | }); 48 | } 49 | @end 50 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugStateMachine.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugStateMachine.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 31.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMStateMachine.h" 10 | 11 | @interface TBSMDebugStateMachine : TBSMStateMachine 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugStateMachine.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugStateMachine.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 31.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMDebugStateMachine.h" 10 | 11 | @implementation TBSMDebugStateMachine 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugSwizzler.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugSwizzler.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 22.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | NS_ASSUME_NONNULL_BEGIN 13 | 14 | @interface TBSMDebugSwizzler : NSObject 15 | 16 | + (void)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector onClass:(Class)class; 17 | @end 18 | NS_ASSUME_NONNULL_END 19 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugSwizzler.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugSwizzler.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 22.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMDebugSwizzler.h" 10 | 11 | @implementation TBSMDebugSwizzler 12 | 13 | + (void)swizzleMethod:(SEL)originalSelector withMethod:(SEL)swizzledSelector onClass:(Class)class 14 | { 15 | Method originalMethod = class_getInstanceMethod(class, originalSelector); 16 | Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector); 17 | 18 | BOOL didAddMethod = 19 | class_addMethod(class, 20 | originalSelector, 21 | method_getImplementation(swizzledMethod), 22 | method_getTypeEncoding(swizzledMethod)); 23 | 24 | if (didAddMethod) { 25 | class_replaceMethod(class, 26 | swizzledSelector, 27 | method_getImplementation(originalMethod), 28 | method_getTypeEncoding(originalMethod)); 29 | } else { 30 | method_exchangeImplementations(originalMethod, swizzledMethod); 31 | } 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugger.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugger.h 3 | // Pods 4 | // 5 | // Created by Julian Krumow on 14.05.17. 6 | // 7 | // 8 | 9 | #import "TBSMEvent+DebugSupport.h" 10 | #import "TBSMState+DebugSupport.h" 11 | #import "TBSMStateMachine+DebugSupport.h" 12 | #import "TBSMTransition+DebugSupport.h" 13 | #import "TBSMDebugStateMachine.h" 14 | #import "TBSMDebugSwizzler.h" 15 | #import "TBSMDebugLogger.h" 16 | 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | FOUNDATION_EXPORT NSString *const TBSMDebugSupportException; 21 | 22 | @interface TBSMDebugger : NSObject 23 | @property (nonatomic, strong) TBSMStateMachine *stateMachine; 24 | 25 | + (instancetype)sharedInstance; 26 | 27 | - (void)debugStateMachine:(TBSMStateMachine *)stateMachine; 28 | - (NSString *)activeStateConfiguration; 29 | - (void)activeStatemachineConfiguration:(TBSMStateMachine *)stateMachine string:(NSMutableString *)string; 30 | @end 31 | NS_ASSUME_NONNULL_END 32 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMDebugger.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMDebugger.m 3 | // Pods 4 | // 5 | // Created by Julian Krumow on 14.05.17. 6 | // 7 | // 8 | 9 | #import "TBSMDebugger.h" 10 | 11 | NSString * const TBSMDebugSupportException = @"TBSMDebugSupportException"; 12 | 13 | @implementation TBSMDebugger 14 | 15 | + (instancetype)sharedInstance 16 | { 17 | static TBSMDebugger *_debugger = nil; 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | _debugger = [TBSMDebugger new]; 21 | }); 22 | return _debugger; 23 | } 24 | 25 | - (void)debugStateMachine:(TBSMStateMachine *)stateMachine 26 | { 27 | if (stateMachine.parentVertex) { 28 | @throw [NSException exceptionWithName:TBSMDebugSupportException reason:@"Debug support not available on sub-statemachines." userInfo:nil]; 29 | } 30 | _stateMachine = stateMachine; 31 | 32 | object_setClass(self.stateMachine, TBSMDebugStateMachine.class); 33 | 34 | [TBSMState activateDebugSupport]; 35 | [TBSMTransition activateDebugSupport]; 36 | [TBSMStateMachine activateDebugSupport]; 37 | } 38 | 39 | - (NSString *)activeStateConfiguration 40 | { 41 | NSMutableString *string = [NSMutableString new]; 42 | [self activeStatemachineConfiguration:self.stateMachine string:string]; 43 | return string; 44 | } 45 | 46 | - (void)activeStatemachineConfiguration:(TBSMStateMachine *)stateMachine string:(NSMutableString *)string 47 | { 48 | TBSMState *state = stateMachine.currentState; 49 | [string appendFormat:@"%@%@\n", [self indentationForLevel:state.path.count-1], stateMachine.name]; 50 | [string appendFormat:@"%@%@\n", [self indentationForLevel:state.path.count], state.name]; 51 | 52 | if ([state isKindOfClass:[TBSMSubState class]]) { 53 | TBSMSubState *subState = (TBSMSubState *)state; 54 | [self activeStatemachineConfiguration:subState.stateMachine string:string]; 55 | } else if ([state isKindOfClass:[TBSMParallelState class]]) { 56 | TBSMParallelState *parallelState = (TBSMParallelState *)state; 57 | for (TBSMStateMachine *subMachine in parallelState.stateMachines) { 58 | [self activeStatemachineConfiguration:subMachine string:string]; 59 | } 60 | } 61 | } 62 | 63 | - (NSString *)indentationForLevel:(NSUInteger)level 64 | { 65 | NSMutableString *indentation = [NSMutableString new]; 66 | for (NSUInteger i=0; i < level-1; i++) { 67 | [indentation appendString:@"\t"]; 68 | } 69 | return indentation; 70 | } 71 | 72 | @end 73 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMEvent+DebugSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEvent+DebugSupport.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 31.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMEvent.h" 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | typedef void (^TBSMDebugCompletionBlock)(void); 14 | 15 | @interface TBSMEvent (DebugSupport) 16 | 17 | @property (nonatomic, copy) TBSMDebugCompletionBlock completionBlock; 18 | @end 19 | NS_ASSUME_NONNULL_END 20 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMEvent+DebugSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMEvent+DebugSupport.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 31.03.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import "TBSMEvent+DebugSupport.h" 12 | 13 | @implementation TBSMEvent (DebugSupport) 14 | @dynamic completionBlock; 15 | 16 | - (TBSMDebugCompletionBlock)completionBlock 17 | { 18 | return objc_getAssociatedObject(self, @selector(completionBlock)); 19 | } 20 | 21 | - (void)setCompletionBlock:(TBSMDebugCompletionBlock)completionBlock 22 | { 23 | objc_setAssociatedObject(self, @selector(completionBlock), completionBlock, OBJC_ASSOCIATION_COPY_NONATOMIC); 24 | } 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMState+DebugSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState+DebugSupport.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 02.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMState.h" 10 | 11 | @interface TBSMState (DebugSupport) 12 | 13 | + (void)activateDebugSupport; 14 | @end 15 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMState+DebugSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMState+DebugSupport.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 02.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMState+DebugSupport.h" 10 | #import "TBSMDebugSwizzler.h" 11 | #import "TBSMDebugLogger.h" 12 | 13 | @implementation TBSMState (DebugSupport) 14 | 15 | + (void)activateDebugSupport 16 | { 17 | static dispatch_once_t onceToken; 18 | dispatch_once(&onceToken, ^{ 19 | 20 | [TBSMDebugSwizzler swizzleMethod:@selector(enter:targetState:data:) withMethod:@selector(tbsm_enter:targetState:data:) onClass:[TBSMState class]]; 21 | [TBSMDebugSwizzler swizzleMethod:@selector(exit:targetState:data:) withMethod:@selector(tbsm_exit:targetState:data:) onClass:[TBSMState class]]; 22 | [TBSMDebugSwizzler swizzleMethod:@selector(hasHandlerForEvent:) withMethod:@selector(tbsm_hasHandlerForEvent:) onClass:[TBSMState class]]; 23 | }); 24 | } 25 | 26 | - (void)tbsm_enter:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 27 | { 28 | [[TBSMDebugLogger sharedInstance] log:@"\tEnter '%@' data: %@", self.name, data]; 29 | [self tbsm_enter:sourceState targetState:targetState data:data]; 30 | } 31 | 32 | - (void)tbsm_exit:(TBSMState *)sourceState targetState:(TBSMState *)targetState data:(id)data 33 | { 34 | [[TBSMDebugLogger sharedInstance] log:@"\tExit '%@' data: %@", self.name, data]; 35 | [self tbsm_exit:sourceState targetState:targetState data:data]; 36 | } 37 | 38 | - (BOOL)tbsm_hasHandlerForEvent:(TBSMEvent *)event 39 | { 40 | BOOL hasHandler = [self tbsm_hasHandlerForEvent:event]; 41 | if (hasHandler) { 42 | [[TBSMDebugLogger sharedInstance] log:@"[%@] will handle event '%@' data: %@", self.name, event.name, event.data]; 43 | } 44 | return hasHandler; 45 | } 46 | 47 | @end 48 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMStateMachine+DebugSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine+DebugSupport.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 19.02.15. 6 | // Copyright (c) 2015 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #include 10 | 11 | #import "TBSMEvent+DebugSupport.h" 12 | #import "TBSMStateMachine.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | @interface TBSMStateMachine (DebugSupport) 16 | @property (nonatomic, strong) NSNumber *millisecondsPerMachTime; 17 | @property (nonatomic, strong) NSMutableArray *eventDebugQueue; 18 | 19 | + (void)activateDebugSupport; 20 | 21 | - (void)scheduleEvent:(TBSMEvent *)event withCompletion:(nullable TBSMDebugCompletionBlock)completion; 22 | @end 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMStateMachine+DebugSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMStateMachine+DebugSupport.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 19.02.15. 6 | // Copyright (c) 2015 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMStateMachine+DebugSupport.h" 10 | #import "TBSMDebugStateMachine.h" 11 | #import "TBSMDebugSwizzler.h" 12 | #import "TBSMDebugLogger.h" 13 | 14 | @implementation TBSMStateMachine (DebugSupport) 15 | @dynamic millisecondsPerMachTime; 16 | @dynamic eventDebugQueue; 17 | 18 | - (NSNumber *)debugSupportEnabled 19 | { 20 | return objc_getAssociatedObject(self, @selector(debugSupportEnabled)); 21 | } 22 | 23 | - (void)setDebugSupportEnabled:(NSNumber *)debugSupportEnabled 24 | { 25 | objc_setAssociatedObject(self, @selector(debugSupportEnabled), debugSupportEnabled, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 26 | } 27 | 28 | - (NSNumber *)millisecondsPerMachTime 29 | { 30 | return objc_getAssociatedObject(self, @selector(millisecondsPerMachTime)); 31 | } 32 | 33 | - (void)setMillisecondsPerMachTime:(NSNumber *)millisecondsPerMachTime 34 | { 35 | objc_setAssociatedObject(self, @selector(millisecondsPerMachTime), millisecondsPerMachTime, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 36 | } 37 | 38 | - (NSMutableArray *)eventDebugQueue 39 | { 40 | NSMutableArray *queue = objc_getAssociatedObject(self, @selector(eventDebugQueue)); 41 | if (queue == nil) { 42 | queue = [NSMutableArray new]; 43 | self.eventDebugQueue = queue; 44 | } 45 | return queue; 46 | } 47 | 48 | - (void)setEventDebugQueue:(NSMutableArray *)eventDebugQueue 49 | { 50 | objc_setAssociatedObject(self, @selector(eventDebugQueue), eventDebugQueue, OBJC_ASSOCIATION_RETAIN_NONATOMIC); 51 | } 52 | 53 | + (void)activateDebugSupport 54 | { 55 | static dispatch_once_t onceToken; 56 | dispatch_once(&onceToken, ^{ 57 | [TBSMDebugSwizzler swizzleMethod:@selector(scheduleEvent:) withMethod:@selector(tbsm_scheduleEvent:) onClass:[TBSMDebugStateMachine class]]; 58 | [TBSMDebugSwizzler swizzleMethod:@selector(handleEvent:) withMethod:@selector(tbsm_handleEvent:) onClass:[TBSMDebugStateMachine class]]; 59 | [TBSMDebugSwizzler swizzleMethod:@selector(setUp:) withMethod:@selector(tbsm_setUp:) onClass:[TBSMStateMachine class]]; 60 | [TBSMDebugSwizzler swizzleMethod:@selector(tearDown:) withMethod:@selector(tbsm_tearDown:) onClass:[TBSMStateMachine class]]; 61 | }); 62 | } 63 | 64 | - (void)tbsm_setUp:(id)data 65 | { 66 | [[TBSMDebugLogger sharedInstance] log:@"[%@] setup data: %@", self.name, data]; 67 | [self tbsm_setUp:data]; 68 | } 69 | 70 | - (void)tbsm_tearDown:(id)data 71 | { 72 | [[TBSMDebugLogger sharedInstance] log:@"[%@] teardown data: %@", self.name, data]; 73 | [self tbsm_tearDown:data]; 74 | } 75 | 76 | - (void)tbsm_scheduleEvent:(TBSMEvent *)event 77 | { 78 | [self.eventDebugQueue addObject:event]; 79 | [self tbsm_scheduleEvent:event]; 80 | } 81 | 82 | - (void)scheduleEvent:(TBSMEvent *)event withCompletion:(TBSMDebugCompletionBlock)completion 83 | { 84 | event.completionBlock = completion; 85 | [self scheduleEvent:event]; 86 | } 87 | 88 | - (BOOL)tbsm_handleEvent:(TBSMEvent *)event 89 | { 90 | [[TBSMDebugLogger sharedInstance] log:@"[%@]: attempt to handle event '%@' data: %@", self.name, event.name, event.data]; 91 | 92 | uint64_t startTime = mach_absolute_time(); 93 | BOOL hasHandledEvent = [self tbsm_handleEvent:event]; 94 | 95 | uint64_t endTime = mach_absolute_time() - startTime; 96 | NSTimeInterval timeInterval = endTime * self.millisecondsPerMachTime.doubleValue; 97 | 98 | [self.eventDebugQueue removeObject:event]; 99 | 100 | [[TBSMDebugLogger sharedInstance] log:@"[%@]: run-to-completion step took %f milliseconds", self.name, timeInterval]; 101 | [[TBSMDebugLogger sharedInstance] log:@"[%@]: remaining events in queue: %i", self.name, self.eventDebugQueue.count]; 102 | [[TBSMDebugLogger sharedInstance] log:@"[%@]: %@\n\n", self.name, [self.eventDebugQueue valueForKeyPath:@"name"]]; 103 | 104 | TBSMDebugCompletionBlock completionBlock = event.completionBlock; 105 | if (completionBlock) { 106 | completionBlock(); 107 | } 108 | return hasHandledEvent; 109 | } 110 | 111 | @end 112 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMTransition+DebugSupport.h: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransition+DebugSupport.h 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 02.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMTransition.h" 10 | 11 | @interface TBSMTransition (DebugSupport) 12 | 13 | + (void)activateDebugSupport; 14 | @end 15 | -------------------------------------------------------------------------------- /Pod/DebugSupport/TBSMTransition+DebugSupport.m: -------------------------------------------------------------------------------- 1 | // 2 | // TBSMTransition+DebugSupport.m 3 | // TBStateMachine 4 | // 5 | // Created by Julian Krumow on 02.04.15. 6 | // Copyright (c) 2014-2017 Julian Krumow. All rights reserved. 7 | // 8 | 9 | #import "TBSMTransition+DebugSupport.h" 10 | #import "TBSMDebugSwizzler.h" 11 | #import "TBSMStateMachine.h" 12 | #import "TBSMDebugLogger.h" 13 | 14 | @implementation TBSMTransition (DebugSupport) 15 | 16 | + (void)activateDebugSupport 17 | { 18 | static dispatch_once_t onceToken; 19 | dispatch_once(&onceToken, ^{ 20 | 21 | [TBSMDebugSwizzler swizzleMethod:@selector(canPerformTransitionWithData:) withMethod:@selector(tbsm_canPerformTransitionWithData:) onClass:[TBSMTransition class]]; 22 | }); 23 | } 24 | 25 | - (BOOL)tbsm_canPerformTransitionWithData:(id)data 26 | { 27 | TBSMStateMachine *lca = nil; 28 | 29 | @try { 30 | lca = [self findLeastCommonAncestor]; 31 | } 32 | @catch (NSException *exception) { 33 | // swallow exception in case lca could not be found since we do not want to interfere with the running application. 34 | } 35 | 36 | BOOL canPerform = [self tbsm_canPerformTransitionWithData:data]; 37 | 38 | if (canPerform) { 39 | [[TBSMDebugLogger sharedInstance] log:@"[%@] performing transition: %@ data: %@", lca.name, self.name, data]; 40 | } 41 | return canPerform; 42 | } 43 | 44 | @end 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TBStateMachine 2 | 3 | [![Version](https://img.shields.io/cocoapods/v/TBStateMachine.svg?style=flat)](http://cocoadocs.org/docsets/TBStateMachine) 4 | [![License](https://img.shields.io/cocoapods/l/TBStateMachine.svg?style=flat)](http://cocoadocs.org/docsets/TBStateMachine) 5 | [![Platform](https://img.shields.io/cocoapods/p/TBStateMachine.svg?style=flat)](http://cocoadocs.org/docsets/TBStateMachine) 6 | 7 | 8 | A lightweight hierarchical state machine framework in Objective-C. 9 | 10 | ## Features 11 | 12 | * Block based API 13 | * Nested states 14 | * Orthogonal regions 15 | * Pseudo states (fork, join and junction) 16 | * External, internal and local transitions with guards and actions 17 | * State switching using least common ancestor algorithm (LCA) 18 | * Thread safe event handling 19 | * Asynchronous event handling 20 | * NSNotificationCenter support 21 | 22 | ![Features](https://raw.githubusercontent.com/jkrumow/TBStateMachine/master/Documentation/test_setup.png) 23 | 24 | ## Requirements 25 | 26 | * iOS 11.0 27 | * macOS 10.11 28 | 29 | ## Installation 30 | 31 | TBStateMachine is available through [CocoaPods](http://cocoapods.org). To install 32 | it, simply add the following line to your Podfile: 33 | 34 | ```ruby 35 | pod 'TBStateMachine' 36 | ``` 37 | 38 | ## Usage 39 | 40 | ### Configuration 41 | 42 | ```objc 43 | #import 44 | ``` 45 | 46 | Create a state, set enter and exit blocks: 47 | 48 | ```objc 49 | TBSMState *a = [TBSMState stateWithName:@"a"]; 50 | a.enterBlock = ^(id data) { 51 | 52 | }; 53 | 54 | a.exitBlock = ^(id data) { 55 | 56 | }; 57 | ``` 58 | 59 | Create a state machine: 60 | 61 | ```objc 62 | TBSMStateMachine *stateMachine = [TBSMStateMachine stateMachineWithName:@"main"]; 63 | ``` 64 | 65 | Add states and set state machine up. The state machine will always set the first state in the given array as the initial state unless you set the initial state explicitly: 66 | 67 | ```objc 68 | stateMachine.states = @[a, b, ...]; 69 | stateMachine.initialState = a; 70 | [stateMachine setUp:nil]; 71 | ``` 72 | 73 | ### Event Handling 74 | 75 | You can add event handlers which trigger transitions to specified target states: 76 | 77 | ```objc 78 | [a addHandlerForEvent:@"transition_1" target:b]; 79 | ``` 80 | 81 | You can also add event handlers with additional action and guard blocks: 82 | 83 | ```objc 84 | 85 | TBSMActionBlock action = ^(id data) { 86 | 87 | }; 88 | 89 | TBSMGuardBlock guard = ^BOOL(id data) { 90 | 91 | return YES; 92 | }; 93 | 94 | [a addHandlerForEvent:@"transition_1" target:b kind:TBSMTransitionExternal action:action guard:guard]; 95 | ``` 96 | 97 | If you register multiple handlers for the same event the guard blocks decide which transition will be fired. 98 | 99 | #### Different Kinds of Transitions 100 | 101 | By default transitions are external. To define a transition kind explicitly choose one of the three kind attributes: 102 | 103 | ``` 104 | TBSMTransitionExternal 105 | TBSMTransitionInternal 106 | TBSMTransitionLocal 107 | ``` 108 | 109 | #### Scheduling Events 110 | 111 | To schedule the event call `scheduleEvent:` and pass the specified `TBSMEvent` instance and (optionally) an object as payload: 112 | 113 | ```objc 114 | TBSMEvent *event = [TBSMEvent eventWithName:@"transition_1" data:aPayloadObject]; 115 | [stateMachine scheduleEvent:event]; 116 | ``` 117 | 118 | The payload will be available in all action, guard, enter and exit blocks which are executed until the event is successfully handled. 119 | 120 | ### Enumerating events 121 | 122 | If you do not want to write string contants for every event like this: 123 | 124 | ```objc 125 | FOUNDATION_EXPORT NSString * const Transition_1; 126 | 127 | NSString * const Transition_1 = @"transition_1"; 128 | ``` 129 | 130 | you can use a struct: 131 | 132 | ```objc 133 | FOUNDATION_EXPORT const struct StateMachineEvents { 134 | __unsafe_unretained NSString *Transition_1; 135 | __unsafe_unretained NSString *Transition_2; 136 | } StateMachineEvents; 137 | 138 | const struct StateMachineEvents StateMachineEvents = { 139 | .Transition_1 = @"transition_1", 140 | .Transition_2 = @"transition_2" 141 | }; 142 | ``` 143 | 144 | And use it like this: 145 | 146 | ```objc 147 | [stateMachine scheduleEventNamed:StateMachineEvents.Transition_1 data:aPayloadObject]; 148 | ``` 149 | 150 | #### Run-to-Completion 151 | 152 | Event processing follows the Run-to-Completion model to ensure that only one event will be handled at a time. A single RTC-step encapsulates the whole logic from evaluating the event to performing the transition to executing guards, actions, exit and enter blocks. 153 | 154 | Events will be queued and processed one after the other. 155 | 156 | ### Nested States 157 | 158 | `TBSMState` instances can also be nested by using `TBSMSubState`: 159 | 160 | ```objc 161 | TBSMSubState *b2 = [TBSMSubState subStateWithName:@"b2"]; 162 | b2.states = @[b21, b22]; 163 | ``` 164 | 165 | You can also register events, add enter and exit blocks on `TBSMSubState`, since it is a subtype of `TBSMState`. 166 | 167 | ### Orthogonal Regions 168 | 169 | To build orthogonal regions you will use `TBSMParallelState`: 170 | 171 | ```objc 172 | TBSMParallelState *b3 = [TBSMParallelState parallelStateWithName:@"b3"]; 173 | b3.states = @[@[b311, b312], @[b321, b322]]; 174 | ``` 175 | 176 | ### Pseudo States 177 | 178 | TBStateMachine supports fork and join pseudo states to construct compound transitions: 179 | 180 | #### Fork 181 | 182 | ```objc 183 | TBSMFork *fork = [TBSMFork forkWithName:@"fork"]; 184 | [a addHandlerForEvent:@"transition_15" target:fork]; 185 | [fork setTargetStates:@[c212, c222] inRegion:c2]; 186 | ``` 187 | 188 | #### Join 189 | 190 | ```objc 191 | TBSMJoin *join = [TBSMJoin joinWithName:@"join"]; 192 | [c212 addHandlerForEvent:@"transition_16" target:join]; 193 | [c222 addHandlerForEvent:@"transition_17" target:join]; 194 | [join setSourceStates:@[c212, c222] inRegion:c2 target:b]; 195 | ``` 196 | 197 | #### Junction 198 | 199 | ```objc 200 | TBSMJunction *junction = [TBSMJunction junctionWithName:@"junction"]; 201 | [a addHandlerForEvent:@"transition_18" target:junction]; 202 | [junction addOutgoingPathWithTarget:b1 action:nil guard:^BOOL(id data) { 203 | return (data[@"goB1"]); 204 | }]; 205 | [junction addOutgoingPathWithTarget:c2 action:nil guard:^BOOL(id data) { 206 | return (data[@"goC2"]); 207 | }]; 208 | ``` 209 | 210 | ### Notifications 211 | 212 | `TBSMState` posts an `NSNotification` on entry and exit: 213 | 214 | * TBSMStateDidEnterNotification 215 | * TBSMStateDidExitNotification 216 | 217 | The notification's `userInfo` contains: 218 | 219 | ```objc 220 | { 221 | TBSMDataUserInfo:theData 222 | } 223 | ``` 224 | 225 | To receive a notification: 226 | 227 | ```objc 228 | [self.stateMachine subscribeToEntryAtPath:@"c/c2@1/c222" forObserver:self selector:@selector(myHandler:)]; 229 | [self.stateMachine subscribeToExitAtPath:@"c/c2@1/c222" forObserver:self selector:@selector(myHandler:)]; 230 | 231 | - (void)myHandler:(NSNotification *)notification 232 | { 233 | id myPayloadObject = notification.userInfo[TBSMDataUserInfo]; 234 | } 235 | ``` 236 | 237 | `TBSMState` also posts an `NSNotification` with the event name when an internal transition has been performed: 238 | 239 | ```objc 240 | [self.stateMachine subscribeToAction:@"transition_10" atPath:@"a/a1" forObserver:self selector:@selector(myHandler:)]; 241 | ``` 242 | 243 | To locate a specified state inside the hierarchy you can use the path scheme seen above. The path consists of names of the states separated by slashes: 244 | 245 | ``` 246 | b/b2/b21 247 | ``` 248 | 249 | In orthogonal regions an `@` sign and an index is added to select the particular child region: 250 | 251 | ``` 252 | c/c2@1/c222 253 | ``` 254 | 255 | ### Configuration via JSON 256 | 257 | Instead of configuring the state machine in code you can define states and transitions via `json`. 258 | `TBSMStateMachineBuilder` reads the `json` file and builds a state machine instance from that: 259 | 260 | ```ruby 261 | pod 'TBStateMachine/Builder' 262 | ``` 263 | 264 | ```objc 265 | #import 266 | 267 | NSString *path = // path to json file 268 | TBMSStateMachine *stateMachine = [TBSMStateMachineBuilder buildFromFile:path]; 269 | ``` 270 | 271 | The format of the configuration file can be seen [in the test fixtures](https://github.com/jkrumow/TBStateMachine/tree/master/Example/Tests/Fixtures). 272 | 273 | The json of configuration file follows the json-schema defined in [Pod/Builder/Schema/schema.json](https://github.com/jkrumow/TBStateMachine/blob/master/Pod/Builder/Schema/schema.json). 274 | 275 | For further information to json schema in general see [http://json-schema.org](http://json-schema.org). 276 | 277 | ### Thread Safety and Concurrency 278 | 279 | `TBStateMachine` is thread safe. Each event is processed asynchronously on the main queue by default. This makes handling of UIKit components convenient. 280 | 281 | To use a dedicated background queue simply set: 282 | 283 | ```objc 284 | NSOperationQueue *queue = [NSOperationQueue new]; 285 | queue.name = @"com.myproject.queue"; 286 | queue.maxConcurrentOperationCount = 1; 287 | stateMachine.scheduledEventsQueue = queue; 288 | ``` 289 | 290 | ### Debug Support 291 | 292 | `TBStateMachine` offers debug support through the subspec `DebugSupport`. Simply add it to your `Podfile` (most likely to a beta target to keep it out of production code): 293 | 294 | ```ruby 295 | target 'MyBetaApp', :exclusive => true do 296 | pod 'TBStateMachine/DebugSupport' 297 | end 298 | ``` 299 | 300 | Then include `TBSMDebugger.h` to the debug the state machine **at the top of the hierarchy**: 301 | 302 | ```objc 303 | #import 304 | 305 | [[TBSMDebugger sharedInstance] debugStateMachine:stateMachine]; 306 | ``` 307 | 308 | The statemachine will then output a log message for every event, transition, setup, teardown, enter and exit including the duration of the performed Run-to-Completion step: 309 | 310 | ``` 311 | [Main]: attempt to handle event 'transition_4' data: 12345 312 | [stateA] will handle event 'transition_4' data: 12345 313 | [Main] performing transition: stateA --> stateCc data: 12345 314 | Exit 'a3' data: 12345 315 | Exit 'a' data: 12345 316 | Enter 'b' data: 12345 317 | Enter 'b2' data: 12345 318 | Enter 'b21' data: 12345 319 | [Main]: run-to-completion step took 1.15 milliseconds 320 | [Main]: remaining events in queue: 1 321 | [Main]: ( 322 | transition_8 323 | ) 324 | ``` 325 | 326 | When calling `-activeStateConfiguration` on the debugger instance you will get the current active state configuration of the whole hierarchy: 327 | 328 | ```objc 329 | NSLog(@"%@", [[TBSMDebugger sharedInstance] activeStateConfiguration]); 330 | ``` 331 | 332 | ``` 333 | Main 334 | b 335 | bSubMachine 336 | b2 337 | b2Submachine 338 | b21 339 | ``` 340 | 341 | ## Development Setup 342 | 343 | Clone the repo and run `pod install` from the `Example` directory first. The project contains a unit test target for development. 344 | 345 | ## Useful Theory on UML State Machines 346 | 347 | - http://en.wikipedia.org/wiki/UML_state_machine 348 | - http://www.omg.org/spec/UML/2.5/Beta2/ 349 | 350 | ## Author 351 | 352 | Julian Krumow, julian.krumow@bogusmachine.com 353 | 354 | ## License 355 | 356 | TBStateMachine is available under the MIT license. See the LICENSE file for more info. 357 | -------------------------------------------------------------------------------- /TBStateMachine.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "TBStateMachine" 3 | s.version = "6.11.0" 4 | s.summary = "A lightweight hierarchical state machine framework in Objective-C." 5 | s.description = <<-DESC 6 | Supports all common features of a UML state machine like: 7 | 8 | - nested states 9 | - orthogonal regions 10 | - pseudo states 11 | - transitions with guards and actions 12 | - state switching using least common ancestor algorithm and run-to-completion model 13 | DESC 14 | s.homepage = "https://github.com/jkrumow/TBStateMachine" 15 | s.license = 'MIT' 16 | s.author = { "Julian Krumow" => "julian.krumow@bogusmachine.com" } 17 | 18 | s.ios.deployment_target = '11.0' 19 | s.osx.deployment_target = '10.11' 20 | 21 | s.requires_arc = true 22 | s.source = { :git => "https://github.com/jkrumow/TBStateMachine.git", :tag => s.version.to_s } 23 | 24 | s.default_subspec = 'Core' 25 | s.subspec 'Core' do |core| 26 | core.source_files = 'Pod/Core' 27 | end 28 | 29 | s.subspec 'Builder' do |builder| 30 | builder.source_files = 'Pod/Builder' 31 | builder.resource_bundle = { 'TBStateMachineBuilder' => 'Pod/Builder/Schema/*.json' } 32 | builder.dependency 'TBStateMachine/Core' 33 | end 34 | 35 | s.subspec 'DebugSupport' do |debug| 36 | debug.source_files = 'Pod/DebugSupport' 37 | debug.dependency 'TBStateMachine/Core' 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /refresh.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | bundle update 4 | cd Example 5 | bundle exec pod update --------------------------------------------------------------------------------