motion-state-machine 0.8.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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