rm-extensions 0.3.3 → 0.4.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 4638058073ecd449cd49a4ae5d0b2c0bcea20304
4
- data.tar.gz: fda67c1392046c93f1c7de0c6de830cf68cd733b
3
+ metadata.gz: 2c40f86893d94e3fadea28f20067e25ca3931f26
4
+ data.tar.gz: b2758a852a8a0a9dd4e47763e6785e509f255bf3
5
5
  SHA512:
6
- metadata.gz: d78724ff4f31f6014c7dc9732cc3a6a80919f3ccc38c0abc2668f51eb21c822b2eafb3e098488a7c12acbb2d708d6b33fca5662ad253c1034d9f34474052263d
7
- data.tar.gz: ea9fb77064f3fb6904c8ec56ab0a50fac3f61b23d41dfeeec5d7ee4051442bb869b2c7a53664d0615dbdbe3f2aae6345cea99572b9f8cd4d8de92e16a34f0086
6
+ metadata.gz: 0d24baa54ceb6ce6b24806f1c9a3723a068c5679420b4328df3f9e5a71b09890e249948965442eabefd02f86ed2ac9f48b1477115a5aeff306d9649e3a80a410
7
+ data.tar.gz: 68224e36927975027038177209c43e4d5eb555e103f05f448cedc34510718019db59dbc0b3574664d0295ff7ee94331201ffe5b41b12eeedff86ff470ff20865
@@ -0,0 +1,207 @@
1
+ module RMExtensions
2
+
3
+ module ObjectExtensions
4
+
5
+ module Events
6
+
7
+ def rmext_events_proxy
8
+ @rmext_events_proxy ||= EventsProxy.new(self)
9
+ end
10
+
11
+ # register a callback when an event is triggered on this object.
12
+ def rmext_on(object, event, &block)
13
+ object.rmext_events_proxy.on(event, inContext:self, withBlock:block)
14
+ end
15
+
16
+ def rmext_now_and_on(object, event, &block)
17
+ object.rmext_events_proxy.now_and_on(event, inContext:self, withBlock:block)
18
+ end
19
+
20
+ # register a callback when an event is triggered on this object and remove it after it fires once
21
+ def rmext_once(object, event, &block)
22
+ object.rmext_events_proxy.once(event, inContext:self, withBlock:block)
23
+ end
24
+
25
+ # remove a specific callback for an event on object
26
+ def rmext_off(object, event, &block)
27
+ if object.rmext_events_proxy?
28
+ object.rmext_events_proxy.off(event, inContext:self, withBlock:block)
29
+ end
30
+ end
31
+
32
+ # remove all event callbacks on this object,
33
+ # remove all event callbacks from other objects in this object's "self"
34
+ def rmext_cleanup
35
+ if @rmext_events_proxy
36
+ @rmext_events_proxy.cleanup
37
+ end
38
+ end
39
+
40
+ ### these get called on the object: ie. @model.rmext_off_all
41
+
42
+ # remove all event callbacks on this object
43
+ def rmext_off_all
44
+ if @rmext_events_proxy
45
+ @rmext_events_proxy.off_all
46
+ end
47
+ end
48
+
49
+ # trigger an event with value on this object
50
+ def rmext_trigger(event, value=nil)
51
+ if @rmext_events_proxy
52
+ @rmext_events_proxy.trigger(event, value)
53
+ end
54
+ end
55
+
56
+ end
57
+
58
+ end
59
+
60
+ class EventResponse
61
+ attr_accessor :context, :value, :target, :event
62
+ end
63
+
64
+ # Proxy class used to hold the actual listeners and contexts where listening
65
+ # and watches for the real class intended to hold the observation to be
66
+ # deallocated, so the events can be cleaned up.
67
+ class EventsProxy
68
+
69
+ def initialize(obj)
70
+ @weak_object = WeakRef.new(obj)
71
+ @desc = obj.inspect
72
+ @events = NSMapTable.weakToStrongObjectsMapTable
73
+ @listenings = NSHashTable.weakObjectsHashTable
74
+ if ::RMExtensions.debug?
75
+ p "created EventsProxy(#{@desc})"
76
+ end
77
+ end
78
+
79
+ def dealloc
80
+ @did_dealloc = true
81
+ cleanup
82
+ if ::RMExtensions.debug?
83
+ p "dealloc EventsProxy(#{@desc})"
84
+ end
85
+ super
86
+ end
87
+
88
+ def cleanup
89
+ off_all
90
+ off_all_context
91
+ true
92
+ end
93
+
94
+ def on(event, inContext:context, withBlock:block)
95
+ return if event.nil? || block.nil?
96
+ event = event.to_s
97
+ context ||= self.class
98
+ unless context_events = @events.objectForKey(context)
99
+ context_events = {}
100
+ @events.setObject(context_events, forKey:context)
101
+ end
102
+ unless context_event_blocks = context_events.objectForKey(event)
103
+ context_event_blocks = []
104
+ context_events.setObject(context_event_blocks, forKey:event)
105
+ end
106
+ block.weak!
107
+ context_event_blocks.addObject block
108
+ # i.e.: controller/view listening_to model
109
+ context.rmext_events_proxy.listening_to(@weak_object)
110
+ end
111
+
112
+ def listening_to(object)
113
+ if ::RMExtensions.debug?
114
+ p "listening_to object", object.class, "from context", @weak_object.class
115
+ end
116
+ @listenings.addObject(object)
117
+ end
118
+
119
+ def now_and_on(event, inContext:context, withBlock:block)
120
+ rmext_inline_or_on_main_q do
121
+ res = EventResponse.new
122
+ res.context = context
123
+ res.value = nil
124
+ res.target = @weak_object
125
+ res.event = event
126
+ block.call(res)
127
+ end
128
+ on(event, inContext:context, withBlock:block)
129
+ end
130
+
131
+ def off(event, inContext:context, withBlock:block)
132
+ return if event.nil? || block.nil?
133
+ event = event.to_s
134
+ context ||= self.class
135
+ return unless context_events = @events.objectForKey(context)
136
+ return unless context_event_blocks = context_events.objectForKey(event)
137
+ context_event_blocks.removeObject block
138
+ nil
139
+ end
140
+
141
+ def once(event, inContext:context, withBlock:block)
142
+ block.weak!
143
+ once_block = lambda do |opts|
144
+ off(event, inContext:context, withBlock:once_block)
145
+ block.call(opts)
146
+ end
147
+ on(event, inContext:context, withBlock:once_block)
148
+ end
149
+
150
+ def off_all
151
+ @events.removeAllObjects
152
+ end
153
+
154
+ def off_context(context)
155
+ @events.setObject(nil, forKey:context)
156
+ end
157
+
158
+ def off_all_context
159
+ while object = @listenings.anyObject
160
+ if ::RMExtensions.debug?
161
+ p "remove object", object.class, "from context", @weak_object.class
162
+ end
163
+ @listenings.removeObject(object)
164
+ object.rmext_events_proxy.off_context(@weak_object)
165
+ end
166
+ end
167
+
168
+ def trigger(event, value)
169
+ # m_desc = nil
170
+ # if ::RMExtensions.debug?
171
+ # m_desc = "~~> EventsProxy(#{@desc})#trigger(#{event}, #{value.inspect.split(" ").first }>)"
172
+ # p "called", m_desc
173
+ # end
174
+ rmext_inline_or_on_main_q do
175
+ next if @did_dealloc
176
+ next if event.nil?
177
+ event = event.to_s
178
+ keyEnumerator = @events.keyEnumerator
179
+ contexts = []
180
+ while context = keyEnumerator.nextObject
181
+ contexts.push context
182
+ end
183
+ while context = contexts.pop
184
+ if context_events = @events.objectForKey(context)
185
+ if event_blocks = context_events[event]
186
+ blocks = [] + event_blocks
187
+ # if ::RMExtensions.debug?
188
+ # p "blocks.size", blocks.size, m_desc
189
+ # end
190
+ while blk = blocks.pop
191
+ res = EventResponse.new
192
+ res.context = context
193
+ res.value = value
194
+ res.target = @weak_object
195
+ res.event = event
196
+ blk.call(res)
197
+ end
198
+ end
199
+ end
200
+ end
201
+ end
202
+ nil
203
+ end
204
+ end
205
+
206
+ end
207
+ Object.send(:include, ::RMExtensions::ObjectExtensions::Events)
@@ -1,3 +1,3 @@
1
1
  module RMExtensions
2
- VERSION = "0.3.3"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/rm-extensions.rb CHANGED
@@ -9,7 +9,7 @@ Motion::Project::App.setup do |app|
9
9
  layout
10
10
  util
11
11
  accessors
12
- observation
12
+ events
13
13
  queues
14
14
  ).reverse.each do |x|
15
15
  app.files.unshift(File.join(File.dirname(__FILE__), "motion/#{x}.rb"))
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rm-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joe Noon
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-01-16 00:00:00.000000000 Z
11
+ date: 2014-01-19 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: Extensions and helpers for dealing with various areas of rubymotion
14
14
  email:
@@ -24,8 +24,8 @@ files:
24
24
  - Rakefile
25
25
  - app/app_delegate.rb
26
26
  - lib/motion/accessors.rb
27
+ - lib/motion/events.rb
27
28
  - lib/motion/layout.rb
28
- - lib/motion/observation.rb
29
29
  - lib/motion/queues.rb
30
30
  - lib/motion/util.rb
31
31
  - lib/rm-extensions.rb
@@ -1,297 +0,0 @@
1
- module RMExtensions
2
-
3
- module ObjectExtensions
4
-
5
- module Observation
6
-
7
- def rmext_observation_proxy
8
- @rmext_observation_proxy ||= ObservationProxy.new(self)
9
- end
10
-
11
- def rmext_observe_passive(object, keyPath:key, withBlock:block)
12
- rmext_observe(object, keyPath:key, options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld), withBlock:block)
13
- end
14
-
15
- def rmext_observe(object, keyPath:key, withBlock:block)
16
- rmext_observe(object, keyPath:key, options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld | NSKeyValueObservingOptionInitial), withBlock:block)
17
- end
18
-
19
- def rmext_observe(object, keyPath:key, options:options, withBlock:block)
20
- rmext_observation_proxy.observe(object, keyPath:key, options:options, withBlock:block)
21
- end
22
-
23
- # unobserve an existing observation
24
- def rmext_unobserve(object, keyPath:key)
25
- if @rmext_observation_proxy
26
- @rmext_observation_proxy.unobserve(object, keyPath:key)
27
- end
28
- end
29
-
30
- # unobserve all existing observations
31
- def rmext_unobserve_all
32
- if @rmext_observation_proxy
33
- @rmext_observation_proxy.unobserve_all
34
- end
35
- end
36
-
37
- # register a callback when an event is triggered on this object.
38
- #
39
- # `inContext` should be passed the object context that the block was originally
40
- # created in. if you are writing the block inline, this would be self. if
41
- # you have a more complicated situation where you are passing the block around as
42
- # an argument from another scope or method, you should take care to also keep
43
- # track of it's context, so you can pass it correctly here.
44
- #
45
- # the context is needed to properly manage memory and let the block "drop out"
46
- # when it's context deallocates, instead of creating leaks.
47
- #
48
- def rmext_on(event, inContext:context, withBlock:block)
49
- rmext_observation_proxy.on(event, inContext:context, withBlock:block)
50
- end
51
-
52
- # register a callback when an event is triggered on this object and remove it after it fires once
53
- # see `#rmext_on` for notes about `inContext`
54
- def rmext_once(event, inContext:context, withBlock:block)
55
- rmext_observation_proxy.once(event, inContext:context, withBlock:block)
56
- end
57
-
58
- # remove a specific callback for an event on this object
59
- # see `#rmext_on` for notes about `inContext`
60
- def rmext_off(event, inContext:context, withBlock:block)
61
- if @rmext_observation_proxy
62
- @rmext_observation_proxy.off(event, inContext:context, withBlock:block)
63
- end
64
- end
65
-
66
- # remove all event callbacks on this object
67
- def rmext_off_all
68
- if @rmext_observation_proxy
69
- @rmext_observation_proxy.off_all
70
- end
71
- end
72
-
73
- # trigger an event with value on this object
74
- def rmext_trigger(event, value=nil)
75
- if @rmext_observation_proxy
76
- @rmext_observation_proxy.trigger(event, value)
77
- end
78
- end
79
-
80
- # remove all observations and event callbacks on this object
81
- def rmext_cleanup
82
- if @rmext_observation_proxy
83
- @rmext_observation_proxy.cleanup
84
- end
85
- end
86
-
87
- end
88
-
89
- end
90
-
91
- class ObservationResponse
92
- attr_accessor :context, :value, :old_value, :target, :key, :indexes, :kind
93
- end
94
-
95
- class EventResponse
96
- attr_accessor :context, :value, :target, :event
97
- end
98
-
99
- # # Proxy class used to hold the actual observation and watches for the real
100
- # # class intended to hold the observation to be deallocated, so the
101
- # # observation can be cleaned up.
102
- class ObservationProxy
103
-
104
- def initialize(obj)
105
- @weak_object = WeakRef.new(obj)
106
- @desc = obj.inspect
107
- @events = NSMapTable.weakToStrongObjectsMapTable
108
- @targets = {}
109
- if ::RMExtensions.debug?
110
- p "created ObservationProxy(#{@desc})"
111
- end
112
- end
113
-
114
- def dealloc
115
- @did_dealloc = true
116
- cleanup
117
- if ::RMExtensions.debug?
118
- p "dealloc ObservationProxy(#{@desc})"
119
- end
120
- super
121
- end
122
-
123
- def cleanup
124
- unobserve_all
125
- off_all
126
- true
127
- end
128
-
129
- def observe(target, keyPath:key_path, options:options, withBlock:block)
130
- already_registered = registered?(target, key_path)
131
- add_observer_block(target, key_path, block)
132
- target.addObserver(self, forKeyPath:key_path, options:options, context:nil) unless already_registered
133
- end
134
-
135
- def unobserve(target, keyPath:key_path)
136
- return unless registered?(target, key_path)
137
- target.removeObserver(self, forKeyPath:key_path)
138
- remove_observer_block(target, key_path)
139
- end
140
-
141
- def remove_observer_block(target, key_path)
142
- return if target.nil? || key_path.nil?
143
- key_path = key_path.to_s
144
- target_hash = @targets[target]
145
- if !target_hash.nil? && target_hash.has_key?(key_path)
146
- target_hash.delete(key_path)
147
- end
148
- end
149
-
150
- def unobserve_all
151
- keys = [] + @targets.keys
152
- while target = keys.pop
153
- target_hash = @targets[target]
154
- paths = [] + target_hash.keys
155
- while key_path = paths.pop
156
- unobserve(target, keyPath:key_path)
157
- end
158
- end
159
- @targets.removeAllObjects
160
- end
161
-
162
- def registered?(target, key_path)
163
- !target.nil? && !@targets[target].nil? && @targets[target].has_key?(key_path.to_s)
164
- end
165
-
166
- def add_observer_block(target, key_path, block)
167
- return if target.nil? || key_path.nil? || block.nil?
168
- key_path = key_path.to_s
169
- @targets[target] ||= {}
170
- @targets[target][key_path] ||= []
171
- block.weak!
172
- @targets[target][key_path].addObject block
173
- end
174
-
175
- # NSKeyValueObserving Protocol
176
-
177
- def observeValueForKeyPath(key_path, ofObject:target, change:change, context:context)
178
- # m_desc = nil
179
- # if ::RMExtensions.debug?
180
- # m_desc = "~~> ObservationProxy(#{@desc})#observeValueForKeyPath(#{key_path}, ofObject:#{target.inspect.split(" ").first}>, ...)"
181
- # p "called", m_desc
182
- # end
183
- action = proc do
184
- next if @did_dealloc
185
- next if target.nil?
186
- key_paths = @targets[target]
187
- next if key_paths.nil?
188
- blocks = key_paths[key_path]
189
- next if blocks.nil?
190
- blocks = [] + blocks # get a new array that can be popped
191
- # if ::RMExtensions.debug?
192
- # p "blocks.size", blocks.size, m_desc
193
- # end
194
- while blk = blocks.pop
195
- res = ObservationResponse.new
196
- res.context = @weak_object
197
- res.value = change[NSKeyValueChangeNewKey]
198
- res.old_value = change[NSKeyValueChangeOldKey]
199
- res.target = target
200
- res.key = key_path
201
- res.indexes = change[NSKeyValueChangeIndexesKey]
202
- res.kind = change[NSKeyValueChangeKindKey]
203
- blk.call(res)
204
- end
205
- end
206
- if NSThread.currentThread.isMainThread
207
- # if ::RMExtensions.debug?
208
- # p "inline execution", m_desc
209
- # end
210
- action.call
211
- else
212
- # if ::RMExtensions.debug?
213
- # p "dispatch execution", m_desc
214
- # end
215
- rmext_on_main_q(&action)
216
- end
217
- end
218
-
219
- def on(event, inContext:context, withBlock:block)
220
- return if event.nil? || block.nil?
221
- event = event.to_s
222
- context ||= self.class
223
- unless context_events = @events.objectForKey(context)
224
- context_events = {}
225
- @events.setObject(context_events, forKey:context)
226
- end
227
- unless context_event_blocks = context_events.objectForKey(event)
228
- context_event_blocks = []
229
- context_events.setObject(context_event_blocks, forKey:event)
230
- end
231
- block.weak!
232
- context_event_blocks.addObject block
233
- end
234
-
235
- def off(event, inContext:context, withBlock:block)
236
- return if event.nil? || block.nil?
237
- event = event.to_s
238
- context ||= self.class
239
- return unless context_events = @events.objectForKey(context)
240
- return unless context_event_blocks = context_events.objectForKey(event)
241
- context_event_blocks.removeObject block
242
- nil
243
- end
244
-
245
- def once(event, inContext:context, withBlock:block)
246
- block.weak!
247
- once_block = lambda do |opts|
248
- off(event, inContext:context, withBlock:once_block)
249
- block.call(opts)
250
- end
251
- on(event, inContext:context, withBlock:once_block)
252
- end
253
-
254
- def off_all
255
- @events.removeAllObjects
256
- end
257
-
258
- def trigger(event, value)
259
- # m_desc = nil
260
- # if ::RMExtensions.debug?
261
- # m_desc = "~~> ObservationProxy(#{@desc})#trigger(#{event}, #{value.inspect.split(" ").first }>)"
262
- # p "called", m_desc
263
- # end
264
- rmext_on_main_q do
265
- next if @did_dealloc
266
- next if event.nil?
267
- event = event.to_s
268
- keyEnumerator = @events.keyEnumerator
269
- contexts = []
270
- while context = keyEnumerator.nextObject
271
- contexts.push context
272
- end
273
- while context = contexts.pop
274
- if context_events = @events.objectForKey(context)
275
- if event_blocks = context_events[event]
276
- blocks = [] + event_blocks
277
- # if ::RMExtensions.debug?
278
- # p "blocks.size", blocks.size, m_desc
279
- # end
280
- while blk = blocks.pop
281
- res = EventResponse.new
282
- res.context = context
283
- res.value = value
284
- res.target = @weak_object
285
- res.event = event
286
- blk.call(res)
287
- end
288
- end
289
- end
290
- end
291
- end
292
- nil
293
- end
294
- end
295
-
296
- end
297
- Object.send(:include, ::RMExtensions::ObjectExtensions::Observation)