motion-state-machine 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,7 @@
1
+ class MotionStateMachineSpecAppDelegate
2
+ def application(application, didFinishLaunchingWithOptions:launchOptions)
3
+ @window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
4
+ @window.rootViewController = UIViewController.alloc.init
5
+ true
6
+ end
7
+ end
@@ -0,0 +1,379 @@
1
+ module StateMachine
2
+ class State
3
+ # @return [Symbol] The state's identifying symbol.
4
+ attr_reader :symbol
5
+
6
+ # @return [StateMachine::Base] the FSM that the state belongs to.
7
+ attr_reader :state_machine
8
+
9
+ # @return [String] the state's name. Only used in debug log output.
10
+ attr_accessor :name
11
+
12
+ # @return [Array] an array of +Proc+ objects called when entering
13
+ # the state.
14
+ attr_accessor :entry_actions
15
+
16
+ # @return [Array] an array of +Proc+ objects called when exiting
17
+ # the state.
18
+ attr_accessor :exit_actions
19
+
20
+
21
+ # @return [Hash] The state machine's internal transition map (event
22
+ # types -> event values -> possible transitions)
23
+ #
24
+ # @example
25
+ # {
26
+ # :on => {
27
+ # :some_event => [transition1, transition2, ...],
28
+ # :other_event => [transition3, transition4, ...],
29
+ # },
30
+ # :after => {
31
+ # 5.0 => [transition5, transition6, ...]
32
+ # }
33
+ # }
34
+
35
+ attr_reader :transition_map
36
+
37
+
38
+ # Initializes a new State.
39
+ #
40
+ # @param [StateMachine] state_machine
41
+ # The state machine that the state belongs to.
42
+ #
43
+ # @param [Hash] options
44
+ # Configuration options for the state.
45
+ #
46
+ # @option options [Symbol] :symbol
47
+ # The state's identifier.
48
+ #
49
+ # @option options [String] :name (nil)
50
+ # The state's name. Only used in debug log output (optional).
51
+ #
52
+ # @example
53
+ # StateMachine::State.new state_machine: my_fsm,
54
+ # :symbol => :doing_something,
55
+ # :name => "doing something very important"
56
+ #
57
+ # @return [StateMachine::State] a new State object
58
+
59
+ def initialize(state_machine, options)
60
+ @state_machine = state_machine
61
+ @symbol = options[:symbol]
62
+ @name = options[:name] || options[:symbol].to_s
63
+ if @symbol.nil? || @state_machine.nil?
64
+ raise ArgumentError, "Missing parameter"
65
+ end
66
+
67
+ @transition_map = {}
68
+ @entry_actions = []
69
+ @exit_actions = []
70
+ end
71
+
72
+
73
+ # @return [Boolean] indicates if the state is a termination state.
74
+
75
+ def terminating?
76
+ !!@terminating
77
+ end
78
+
79
+ def terminating=(value)
80
+ @terminating = !!value
81
+ end
82
+
83
+ # @api private
84
+ # Registers a transition in the transition map.
85
+ #
86
+ # @param [Transition] transition the transition to register.
87
+
88
+ def register(transition)
89
+ event_type = transition.class.instance_variable_get(:@event_type)
90
+ event_trigger_value = transition.event_trigger_value
91
+
92
+ transition_map[event_type] ||= {}
93
+
94
+ transitions =
95
+ (transition_map[event_type][event_trigger_value] ||= [])
96
+ transitions << transition
97
+
98
+ transition
99
+ end
100
+
101
+ class TransitionDefinitionDSL
102
+
103
+ # Initializes a new object that provides methods for configuring
104
+ # state transitions.
105
+ #
106
+ # See {Base#when} for an explanation how to use the DSL.
107
+ #
108
+ # @param [State] source_state
109
+ # The source state in which the transitions begin.
110
+ #
111
+ # @param [StateMachine] state_machine
112
+ # The state machine in which the transitions should be defined.
113
+ #
114
+ # @yieldparam [TransitionDefinitionDSL] state
115
+ # The DSL object. Call methods on this object to define
116
+ # transitions.
117
+ #
118
+ # @return [StateMachine::State::TransitionDefinitionDSL]
119
+ # the initialized object.
120
+ #
121
+ # @api private
122
+ # @see Base#when
123
+
124
+ def initialize(source_state, state_machine, &block)
125
+ @state_machine = state_machine
126
+ @state = source_state
127
+ yield(self)
128
+ end
129
+
130
+ # Creates transitions to another state when defined events happen.
131
+ #
132
+ # If multiple trigger events are defined, any of them will create
133
+ # its own {Transition} object.
134
+ #
135
+ # You can specify guard blocks that can prevent a transition from
136
+ # happening.
137
+ #
138
+ # @param options [Hash]
139
+ # Configuration options for the transition.
140
+ #
141
+ # @option options [Symbol] :on
142
+ # Event symbol to trigger the transition via {Base#event}.
143
+ #
144
+ # @option options [String] :on_notification
145
+ # +NSNotification+ name that triggers the transition if posted
146
+ # via default +NSNotificationCenter+.
147
+ #
148
+ # @option options [Float] :after
149
+ # Defines a timeout after which the transition occurs if the
150
+ # state is not left before. Given in seconds.
151
+ #
152
+ # @option options [Proc] :if (nil)
153
+ # Block that should return a +Boolean+. Return +false+ in this
154
+ # block to prevent the transition from happening.
155
+ #
156
+ # @option options [Proc] :unless (nil)
157
+ # Block that should return a +Boolean+. Return +true+ in this
158
+ # block to prevent the transition from happening.
159
+ #
160
+ # @option options [Proc] :action (nil)
161
+ # Block that is executed after exiting the source state and
162
+ # before entering the destination state. Will be called with
163
+ # the state machine as first parameter.
164
+ #
165
+ # @option options [Boolean] :internal (false)
166
+ # For a transition from a state to itself: If +true+, the
167
+ # transition does not call entry/exit actions on the state
168
+ # when executed.
169
+ #
170
+ # @example
171
+ # fsm.when :loading do |state|
172
+ # state.transition_to :displaying_data,
173
+ # on: :data_loaded,
174
+ # if: proc { data.valid? },
175
+ # action: proc { dismiss_loading_indicator }
176
+ # end
177
+ #
178
+ # @return [Array<StateMachine::Transition>] an array of all
179
+ # created transitions.
180
+ #
181
+ # @see Base#when
182
+
183
+ def transition_to(destination_state_symbol, options)
184
+ unless destination_state_symbol.is_a? Symbol
185
+ raise ArgumentError,
186
+ "No destination state given "\
187
+ "(got #{destination_state_symbol.inspect})"
188
+ end
189
+
190
+ options.merge! from: @state.symbol, to: destination_state_symbol
191
+
192
+ defined_event_types = Transition.types.select do |type|
193
+ !options[type].nil?
194
+ end
195
+
196
+ if defined_event_types.empty?
197
+ raise ArgumentError,
198
+ "No trigger event found in #{options}. "\
199
+ "Valid trigger event keys: #{Transition.types}."
200
+ end
201
+
202
+ transitions = []
203
+
204
+ defined_event_types.each do |type|
205
+ event_trigger_value = options[type]
206
+ if event_trigger_value
207
+ options.merge! state_machine: @state_machine,
208
+ type: type
209
+ transition = Transition.make options
210
+ @state.register(transition)
211
+ transitions << transition
212
+ end
213
+ end
214
+
215
+ transitions
216
+ end
217
+
218
+
219
+ # Defines a transition to a terminating state when a specified
220
+ # event happens. Works analog to {#transition_to}, but creates a
221
+ # terminating destination state automatically.
222
+ #
223
+ # @return [Array<StateMachine::Transition>]
224
+ # an array of all transitions that are defined in the option
225
+ # array (e.g. two transitions if you define an +:on+ and an
226
+ # +:after+ option, but no +:on_notification+ option).
227
+ #
228
+ # @see Base#when
229
+
230
+ def die(options)
231
+ termination_states = @state_machine.states.select(&:terminating?)
232
+ symbol = "terminated_#{termination_states.count}".to_sym
233
+
234
+ termination_state = @state_machine.state symbol
235
+ termination_state.terminating = true
236
+
237
+ transitions = transition_to(symbol, options)
238
+ event_texts = transitions.collect(&:event_log_text).join(" or ")
239
+ termination_state.name =
240
+ "terminated (internal state) #{event_texts}"
241
+
242
+ transitions
243
+ end
244
+
245
+
246
+ # Defines a block that will be called without parameters when the
247
+ # source state is entered.
248
+ #
249
+ # @see Base#when
250
+
251
+ def on_entry(&block)
252
+ @state.entry_actions << block
253
+ end
254
+
255
+
256
+ # Defines a block that will be called without parameters when the
257
+ # source state is exited.
258
+ #
259
+ # @see Base#when
260
+
261
+ def on_exit(&block)
262
+ @state.exit_actions << block
263
+ end
264
+
265
+ end
266
+
267
+
268
+ private
269
+
270
+
271
+ # Adds the outgoing transitions defined in the given block to the
272
+ # state.
273
+
274
+ def add_transition_map_defined_in(&block)
275
+ TransitionDefinitionDSL.new self, @state_machine, &block
276
+ end
277
+
278
+
279
+ # Sets the state machine's current_state to self, calls all entry
280
+ # actions and activates triggering mechanisms of all outgoing
281
+ # transitions.
282
+
283
+ def enter!
284
+ @state_machine.current_state = self
285
+
286
+ @entry_actions.each do |entry_action|
287
+ entry_action.call(@state_machine)
288
+ end
289
+ @transition_map.each do |type, events_to_transition_arrays|
290
+ events_to_transition_arrays.each do |event, transitions|
291
+ transitions.each(&:arm)
292
+ end
293
+ end
294
+ end
295
+
296
+
297
+ # Sets the state machine's current_state to nil, calls all exit
298
+ # actions and deactivates triggering mechanisms of all outgoing
299
+ # transitions.
300
+
301
+ def exit!
302
+ map = @transition_map
303
+ map.each do |type, events_to_transition_arrays|
304
+ events_to_transition_arrays.each do |event, transitions|
305
+ transitions.each(&:unarm)
306
+ end
307
+ end
308
+
309
+ @exit_actions.each do |exit_action|
310
+ exit_action.call(@state_machine)
311
+ end
312
+ @state_machine.current_state = nil
313
+ end
314
+
315
+
316
+ # Cleans up references to allow easier GC.
317
+
318
+ def cleanup
319
+ @transition_map.each do |type, events_to_transition_arrays|
320
+ events_to_transition_arrays.each do |event, transitions|
321
+ transitions.clear
322
+ end
323
+ end
324
+
325
+ @transition_map = nil
326
+ @state_machine = nil
327
+ @entry_actions = nil
328
+ @exit_actions = nil
329
+ end
330
+
331
+
332
+ # Executes the registered transition for the given event type and
333
+ # event trigger value, if such a transition exists and it is
334
+ # allowed.
335
+ #
336
+ # @raise [RuntimeError] if multiple transitions would be allowed at
337
+ # the same time.
338
+
339
+ def guarded_execute(event_type, event_trigger_value)
340
+ @state_machine.raise_outside_initial_queue
341
+
342
+ return if terminating?
343
+
344
+ if @transition_map[event_type].nil? ||
345
+ @transition_map[event_type][event_trigger_value].nil?
346
+ raise ArgumentError,
347
+ "No registered transition found "\
348
+ "for event #{event_type}:#{event_trigger_value}."
349
+ end
350
+
351
+ possible_transitions =
352
+ @transition_map[event_type][event_trigger_value]
353
+
354
+ unless possible_transitions.empty?
355
+ allowed_transitions = possible_transitions.select(&:allowed?)
356
+
357
+ if allowed_transitions.empty?
358
+ @state_machine.log "All transitions are disallowed for "\
359
+ "#{event_type}:#{event_trigger_value}."
360
+ elsif allowed_transitions.count > 1
361
+ list = allowed_transitions.collect do |t|
362
+ "-> #{t.options[:to]}"
363
+ end
364
+ raise RuntimeError,
365
+ "Not sure which transition to trigger "\
366
+ "when #{symbol} while #{self} (allowed: #{list}). "\
367
+ "Please make sure guard conditions exclude each other."
368
+ else
369
+ transition = allowed_transitions.first
370
+ unless transition.nil?
371
+ transition.send :unguarded_execute
372
+ end
373
+ end
374
+
375
+ end
376
+ end
377
+
378
+ end
379
+ end
@@ -0,0 +1,407 @@
1
+ module StateMachine
2
+
3
+ # See subclasses for various transition implementations.
4
+ # Sorry for putting multiple classes in one file —
5
+ # RubyMotion has no decentral dependency management yet...
6
+
7
+ # @abstract Subclass and override {#event_description}, {#arm} and {#unarm} to implement a custom Transition class.
8
+
9
+ class Transition
10
+
11
+ # @return [Hash] configuration options of the transition.
12
+ attr_reader :options
13
+
14
+ # @return [Base] the state machine that this transition belongs to.
15
+ attr_reader :state_machine
16
+
17
+ # @return [State] the state from which the transition starts.
18
+ attr_reader :source_state
19
+
20
+ # @return [State] the state that the transition leads to.
21
+ attr_reader :destination_state
22
+
23
+ # @return [Object] a more specific object that triggers
24
+ # the transition.
25
+ attr_reader :event_trigger_value
26
+
27
+
28
+ class << self
29
+ # @return [Symbol] Metaclass attribute, contains the key that
30
+ # is used for generating the specific transition via {#make}.
31
+ attr_accessor :event_type
32
+ end
33
+
34
+
35
+ @@types_to_subclasses = {}
36
+
37
+ # @return [Array<Class<Transition>>] a list of all registered transition subclasses
38
+ def self.types
39
+ @@types_to_subclasses.keys
40
+ end
41
+
42
+
43
+ # Creates a new {Transition} object with the given options.
44
+ # The returned object's subclass is determined by the
45
+ # +:type+ option.
46
+ #
47
+ # @param options [Hash] Configuration options for the transition.
48
+ # @option options [Symbol] :type Type identifier for the transition, ex. +:on+, +:after+, +:on_notification+.
49
+ #
50
+ # See {#initialize} for all possible options.
51
+ #
52
+ # @example
53
+ # StateMachine::Transition.make type: :to, ... # => #<StateMachine::Transition:...>
54
+ # @return [Transition] a new object with the class that fits the given +:type+ option.
55
+
56
+ def self.make(options)
57
+ klass = @@types_to_subclasses[options[:type]]
58
+ klass.new options
59
+ end
60
+
61
+
62
+ # Initializes a new {Transition} between two given states.
63
+ #
64
+ # Additionally, you must also supply an event trigger value as
65
+ # option. Its key must be equal to the transition class's
66
+ # +event_type+., ex. if +event_type+ is +:on+, you have to supply
67
+ # the event value using the option key +:on+.
68
+ #
69
+ # @param options [Hash] Configuration options for the transition.
70
+ #
71
+ # @option options [StateMachine::Base] :state_machine
72
+ # State machine that the transition belongs to.
73
+ #
74
+ # @option options [Symbol] :from
75
+ # State where the transition begins.
76
+ #
77
+ # @option options [Symbol] :to
78
+ # State where the transition ends.
79
+ #
80
+ # @option options [Proc] :if (nil)
81
+ # Block that should return a +Boolean+. If the block returns
82
+ # +false+, the transition will be blocked and not executed
83
+ # (optional).
84
+ #
85
+ # @option options [Proc] :unless (nil)
86
+ # Block that should return a +Boolean+. If the block returns
87
+ # +true+, the transition will be blocked and not executed
88
+ # (optional).
89
+ #
90
+ # @option options [Boolean] :internal (false)
91
+ # If set to true, the transition will not leave it's source
92
+ # state: Entry and Exit actions will not be called in this case.
93
+ # For internal transitions, +:from+ and +:to+ must be the same
94
+ # (optional).
95
+ #
96
+ # @note This method should not be used directly. To create +Transition+ objects, use {#make} instead.
97
+
98
+ def initialize(options)
99
+ @options = options.dup
100
+ @state_machine = @options.delete :state_machine
101
+ @source_state = @state_machine.state options[:from]
102
+ @destination_state = @state_machine.state options[:to]
103
+
104
+ event_type = self.class.event_type
105
+ if event_type.nil?
106
+ raise RuntimeError, "#{self.class} has no defined event type."
107
+ end
108
+
109
+ [:from, :to].each do |symbol|
110
+ unless options[symbol].is_a?(Symbol)
111
+ raise ":#{symbol} option must be given as symbol."
112
+ end
113
+ end
114
+
115
+ @event_trigger_value = options[event_type]
116
+ if @event_trigger_value.nil?
117
+ raise ArgumentError, "You must supply an event trigger value."
118
+ end
119
+
120
+ if options[:internal] && options[:from] != options[:to]
121
+ raise ArgumentError,
122
+ "Internal states must have same source and destination state."
123
+ end
124
+ end
125
+
126
+
127
+ # @return [Boolean] Asks the guard blocks given for +:if+ and
128
+ # +:unless+ if the transition is allowed. Returns +true+ if the
129
+ # transition is allowed to be executed.
130
+
131
+ def allowed?
132
+ if_guard = options[:if]
133
+ unless if_guard.nil?
134
+ return false unless if_guard.call(@state_machine)
135
+ end
136
+ unless_guard = options[:unless]
137
+ unless unless_guard.nil?
138
+ return false if unless_guard.call(@state_machine)
139
+ end
140
+ true
141
+ end
142
+
143
+
144
+ # @return [String] a short description of the event.
145
+ # Used for debug output.
146
+
147
+ def event_description
148
+ # Implement this in a subclass.
149
+ "after unclassified event"
150
+ end
151
+
152
+
153
+ def inspect
154
+ "#<#{self.class.name}:0x#{object_id.to_s(16)} "\
155
+ "#{event_description} @options=#{options.inspect}>"
156
+ end
157
+
158
+
159
+ protected
160
+
161
+
162
+ # Defines a +Hash+ key symbol that is unique to a {Transition}
163
+ # subclass. The key is used by {#make} to identify which
164
+ # {Transition} subclass should be created.
165
+ #
166
+ # @param [Symbol] type_symbol
167
+ # Unique symbol identifying your transition subclass.
168
+
169
+ def self.type(type_symbol)
170
+ unless type_symbol.is_a?(Symbol)
171
+ raise ArgumentError, "Type must be given as symbol."
172
+ end
173
+ if @@types_to_subclasses[type_symbol].nil?
174
+ @@types_to_subclasses[type_symbol] = self
175
+ else
176
+ other_class = @@types_to_subclasses[type_symbol]
177
+ raise ArgumentError,
178
+ "Can't register :#{type_symbol} twice, "
179
+ "already used by #{other_class}."
180
+ end
181
+ @event_type = type_symbol
182
+ end
183
+
184
+
185
+ # Delegates handling the transition to the source state, which
186
+ # makes sure that there are no ambiguous transitions for the
187
+ # same event.
188
+
189
+ def handle_in_source_state
190
+ if @state_machine.initial_queue.nil?
191
+ raise RuntimeError, "State machine not started yet."
192
+ end
193
+
194
+ if Dispatch::Queue.current.to_s != @state_machine.initial_queue.to_s
195
+ raise RuntimeError,
196
+ "#{self.class.event_type}:#{@event_trigger_value} must be "\
197
+ "called from the queue where the state machine was started."
198
+ end
199
+
200
+ @source_state.send :guarded_execute,
201
+ self.class.event_type,
202
+ @event_trigger_value
203
+ end
204
+
205
+
206
+ private
207
+
208
+
209
+ # Executed the transition directly, without checking the guard
210
+ # blocks. Calls {State#exit!} on the source state, executes
211
+ # the transition's +:action+ block and calls {State#enter!} on
212
+ # the destination state.
213
+
214
+ def unguarded_execute
215
+ @source_state.send :exit! unless @options[:internal] == true
216
+ # Could use @state_machine.instance_eval(&options[:action]) here,
217
+ # but this would be much slower
218
+ @options[:action] && @options[:action].call(@state_machine)
219
+ @destination_state.send :enter! unless @options[:internal] == true
220
+
221
+ @state_machine.log "#{event_log_text}"
222
+ end
223
+
224
+
225
+ # @return [String] Debug string that can be logged after the
226
+ # transition has been executed.
227
+
228
+ def event_log_text
229
+ if @options[:internal]
230
+ "#{@state_machine.name} still #{destination_state.name} "\
231
+ "#{event_description} (internal transition, not exiting state)."
232
+ else
233
+ "#{@state_machine.name} #{destination_state.name} "\
234
+ "#{event_description}."
235
+ end
236
+ end
237
+
238
+
239
+ # Called by source {State} when it is entered. Allows the
240
+ # transition to initialize a mechanism that catches its trigger
241
+ # event. Override this in a subclass.
242
+
243
+ def arm
244
+ end
245
+
246
+
247
+ # Called by source {State} when it is exited. Subclass
248
+ # implementations should deactivate their triggering
249
+ # mechanism in this method.
250
+
251
+ def unarm
252
+ end
253
+
254
+ end
255
+
256
+
257
+ # This is kind of the 'standard' transition. It is created when
258
+ # supplying the +:on+ option in the state machine definition.
259
+ #
260
+ # If armed, the transition is triggered when sending an event to the
261
+ # state machine by calling {StateMachine::Base#event} with the
262
+ # event's symbol as parameter. Sending the same event symbol while
263
+ # not armed will just ignore the event.
264
+ #
265
+ # Note that you should call the {StateMachine::Base#event} method
266
+ # from the same queue / thread where the state machine was started.
267
+ #
268
+ # @example Create a {SendEventTransition}:
269
+ #
270
+ # state_machine.when :sleeping do |state|
271
+ # state.transition_to :awake, on: :foo
272
+ # end
273
+ #
274
+ # state_machine.event :foo
275
+ # # => state machine goes to :awake state
276
+
277
+ class SendEventTransition < Transition
278
+ type :on
279
+
280
+ def initialize(options)
281
+ super(options)
282
+ unarm
283
+ end
284
+
285
+ def event_description
286
+ "after #{event_trigger_value}"
287
+ end
288
+
289
+ def arm
290
+ state_machine.register_event_handler event_trigger_value, self
291
+ end
292
+
293
+ def unarm
294
+ state_machine.register_event_handler event_trigger_value, nil
295
+ end
296
+ end
297
+
298
+
299
+ # Transitions of this type are triggered on a given timeout (in
300
+ # seconds). Created when supplying an :after option in the transition
301
+ # definition.
302
+ #
303
+ # The timeout is canceled when the state is left.
304
+ #
305
+ # The transition uses Grand Central Dispatch's timer source
306
+ # mechanism: It adds a timer source to the state machine's initial
307
+ # GCD queue.
308
+ #
309
+ # The system tries to achieve an accuracy of 1 millisecond for the
310
+ # timeout. You can change this behavior to trade timing accuracy vs.
311
+ # system performance by using the +leeway+ option (given in seconds).
312
+ # See {http://developer.apple.com/mac/library/DOCUMENTATION/Darwin/Reference/ManPages/man3/dispatch_source_set_timer.3.html Apple's GCD documentation}
313
+ # for more information about this parameter.
314
+ #
315
+ # @example Create a transition that timeouts after 8 hours:
316
+ #
317
+ # state_machine.when :sleeping do |state|
318
+ # # Timeout after 28800 seconds
319
+ # state.transition_to :awake, after: 8 * 60 * 60
320
+ # end
321
+
322
+ class TimedTransition < Transition
323
+ type :after
324
+
325
+ def event_description
326
+ "after #{event_trigger_value} seconds of "\
327
+ "#{source_state.name} (timeout)"
328
+ end
329
+
330
+ #
331
+ def arm
332
+ @state_machine.log "Starting timeout -> #{options[:to]}, "\
333
+ "after #{options[:after]}"
334
+ delay = event_trigger_value
335
+ interval = 0
336
+ leeway = @options[:leeway] || 0.001
337
+ queue = @state_machine.initial_queue
338
+ @timer = Dispatch::Source.timer(delay, interval, leeway, queue) do
339
+ @state_machine.log "Timeout!"
340
+ self.handle_in_source_state
341
+ end
342
+ end
343
+
344
+ def unarm
345
+ @state_machine.log "Timer unarmed"
346
+ @timer.cancel!
347
+ end
348
+
349
+ end
350
+
351
+
352
+ # Triggered on a specified +NSNotification+ name. Created when
353
+ # supplying an +:on_notification+ option in the transition definition.
354
+ #
355
+ # On entering the source state, the transition registers itself as
356
+ # +NSNotification+ observer on the default +NSNotificationCenter+. It
357
+ # deregisters when the state is exited.
358
+ #
359
+ # @example
360
+ # state_machine.when :awake do |state|
361
+ # state.transition_to :sleeping,
362
+ # on_notificaiton: UIApplicationDidEnterBackgroundNotification
363
+ # end
364
+
365
+ class NotificationTransition < Transition
366
+ type :on_notification
367
+
368
+ def initialize(options)
369
+ super options
370
+ end
371
+
372
+ def event_description
373
+ "after getting a #{event_trigger_value}"
374
+ end
375
+
376
+ def arm
377
+ NSNotificationCenter.defaultCenter.addObserver self,
378
+ selector: :handle_in_initial_queue,
379
+ name:event_trigger_value,
380
+ object:nil
381
+ @state_machine.log "Registered notification #{event_trigger_value}"
382
+ end
383
+
384
+ def unarm
385
+ NSNotificationCenter.defaultCenter.removeObserver self
386
+ @state_machine.log "Removed as observer"
387
+ end
388
+
389
+
390
+ private
391
+
392
+ # This makes sure that state entry/exit and transition actions are
393
+ # called within the initial queue/thread.
394
+
395
+ def handle_in_initial_queue
396
+ if state_machine.initial_queue.to_s == Dispatch::Queue.main.to_s
397
+ handle_in_source_state
398
+ else
399
+ state_machine.initial_queue.async do
400
+ handle_in_source_state
401
+ end
402
+ end
403
+ end
404
+
405
+ end
406
+
407
+ end