rm-extensions 0.3.3 → 0.4.0

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