1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
|
//
// SDLStateMachine.m
// SmartDeviceLink-iOS
//
// Created by Joel Fischer on 10/30/15.
// Copyright © 2015 smartdevicelink. All rights reserved.
//
#import "SDLStateMachine.h"
#import "SDLError.h"
NS_ASSUME_NONNULL_BEGIN
NSString *const SDLStateMachineNotificationFormat = @"com.sdl.statemachine.%@";
SDLStateMachineNotificationInfoKey const SDLStateMachineNotificationInfoKeyOldState = @"oldState";
SDLStateMachineNotificationInfoKey const SDLStateMachineNotificationInfoKeyNewState = @"newState";
SDLStateMachineExceptionInfoKey const SDLStateMachineExceptionInfoKeyTargetClass = @"targetClass";
SDLStateMachineExceptionInfoKey const SDLStateMachineExceptionInfoKeyFromState = @"fromState";
SDLStateMachineExceptionInfoKey const SDLStateMachineExceptionInfoKeyToClass = @"toState";
SDLStateMachineTransitionFormat const SDLStateMachineTransitionFormatWillLeave = @"willLeaveState%@";
SDLStateMachineTransitionFormat const SDLStateMachineTransitionFormatWillTransition = @"willTransitionFromState%@ToState%@";
SDLStateMachineTransitionFormat const SDLStateMachineTransitionFormatDidTransition = @"didTransitionFromState%@ToState%@";
SDLStateMachineTransitionFormat const SDLStateMachineTransitionFormatDidEnter = @"didEnterState%@";
@interface SDLStateMachine ()
@property (copy, nonatomic, readwrite) SDLState *currentState;
@end
@implementation SDLStateMachine
- (instancetype)initWithTarget:(id)target initialState:(SDLState *)initialState states:(NSDictionary<SDLState *, SDLAllowableStateTransitions *> *)states {
self = [super init];
if (!self) {
return nil;
}
if (states[initialState] == nil) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Attempted to start with an SDLState that is not in the states dictionary" userInfo:nil];
}
_target = target;
_states = states;
_currentState = initialState;
return self;
}
- (void)transitionToState:(SDLState *)state {
NSString *oldState = [self.currentState copy];
if ([self isCurrentState:state]) {
return;
}
if (![self sdl_canState:self.currentState transitionToState:state]) {
@throw [NSException exceptionWithName:NSInternalInconsistencyException
reason:@"Invalid state machine transition occurred"
userInfo:@{SDLStateMachineExceptionInfoKeyTargetClass: NSStringFromClass([self.target class]),
SDLStateMachineExceptionInfoKeyFromState: self.currentState,
SDLStateMachineExceptionInfoKeyToClass: state}];
}
SEL willLeave = NSSelectorFromString([NSString stringWithFormat:SDLStateMachineTransitionFormatWillLeave, oldState]);
SEL willTransition = NSSelectorFromString([NSString stringWithFormat:SDLStateMachineTransitionFormatWillTransition, oldState, state]);
SEL didTransition = NSSelectorFromString([NSString stringWithFormat:SDLStateMachineTransitionFormatDidTransition, oldState, state]);
SEL didEnter = NSSelectorFromString([NSString stringWithFormat:SDLStateMachineTransitionFormatDidEnter, state]);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
// Pre state transition calls
if ([self.target respondsToSelector:willLeave]) {
[self.target performSelector:willLeave];
}
if ([self.target respondsToSelector:willTransition]) {
[self.target performSelector:willTransition];
}
// Transition the state
self.currentState = state;
// Post state transition calls
[[NSNotificationCenter defaultCenter] postNotificationName:self.transitionNotificationName object:self userInfo:@{SDLStateMachineNotificationInfoKeyOldState: oldState, SDLStateMachineNotificationInfoKeyNewState: state}];
if ([self.target respondsToSelector:didTransition]) {
[self.target performSelector:didTransition];
}
if ([self.target respondsToSelector:didEnter]) {
[self.target performSelector:didEnter];
}
#pragma clang diagnostic pop
}
- (BOOL)isCurrentState:(SDLState *)state {
return [self.currentState isEqualToString:state];
}
#pragma mark - Helpers
- (void)setToState:(SDLState *)state {
if (![self.states.allKeys containsObject:state]) {
return;
}
self.currentState = state;
}
/**
* Determine if a state transition is valid. Returns YES if the state transition dictionary's fromState key contains toState in its value array, or if fromState and toState are the same state.
*
* @param fromState The former state
* @param toState The new state
*
* @return Whether or not the state transition is valid
*/
- (BOOL)sdl_canState:(SDLState *)fromState transitionToState:(SDLState *)toState {
if ([self.states[fromState] containsObject:toState] || [fromState isEqualToString:toState]) {
return YES;
}
return NO;
}
- (NSString *)transitionNotificationName {
return [NSString stringWithFormat:SDLStateMachineNotificationFormat, [self.target class]];
}
@end
NS_ASSUME_NONNULL_END
|