rm-extensions 0.0.1 → 0.0.2

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.
@@ -3,6 +3,25 @@ module RMExtensions
3
3
  # this module is included on Object, so these methods are available from anywhere in your code.
4
4
  module ObjectExtensions
5
5
 
6
+ def rmext_weak_attr_accessor(*attrs)
7
+ attrs.each do |attr|
8
+ define_method(attr) do
9
+ if val = instance_variable_get("@#{attr}")
10
+ val.nonretainedObjectValue
11
+ end
12
+ end
13
+ define_method("#{attr}=") do |val|
14
+ if val.nil?
15
+ instance_variable_set("@#{attr}", nil)
16
+ else
17
+ # should we do an rmext_on_dealloc on the val?
18
+ instance_variable_set("@#{attr}", NSValue.valueWithNonretainedObject(val))
19
+ end
20
+ val
21
+ end
22
+ end
23
+ end
24
+
6
25
  def rmext_assert_main_thread!
7
26
  raise "This method must be called on the main thread." unless NSThread.currentThread.isMainThread
8
27
  end
@@ -71,6 +90,11 @@ module RMExtensions
71
90
  end
72
91
  end
73
92
 
93
+ # #rmext_context yields an object you can treat like an openstruct (the "context")
94
+ def rmext_context(&block)
95
+ ::RMExtensions::Context.create(self, &block)
96
+ end
97
+
74
98
  # #rmext_retained_context yields an object you can treat like an openstruct. you can get/set any
75
99
  # property on it. the context is globally retained, until #detach! is called on the context.
76
100
  # this convention should fill the gap where local variables and scope bugs currently occur in RM,
@@ -100,16 +124,90 @@ module RMExtensions
100
124
  #
101
125
  # experimental feature:
102
126
  #
103
- # you can call #beginBackground! on the context, and it will check-out a background task identifier,
127
+ # you can call #begin_background! on the context, and it will check-out a background task identifier,
104
128
  # and automatically end the background task when you call #detach! as normal.
105
129
  def rmext_retained_context(&block)
106
- ::RMExtensions::RetainedContext.retained(self, &block)
130
+ ::RMExtensions::RetainedContext.create(self, &block)
131
+ end
132
+
133
+
134
+ def rmext_observe(object, key, &block)
135
+ # p "+ rmext_observe", self, object, key
136
+ rmext_observe_passive(object, key, &block)
137
+ block.call(object.send(key)) unless block.nil?
138
+ end
139
+
140
+ def rmext_observe_passive(object, key, &block)
141
+ wop = ::RMExtensions::WeakObserverProxy.get(self)
142
+ b = -> (old_value, new_value) do
143
+ block.call(new_value) unless block.nil?
144
+ end
145
+ wop.observe(object, key, &b)
146
+ end
147
+
148
+ def rmext_unobserve(object, key)
149
+ wop = ::RMExtensions::WeakObserverProxy.get(self)
150
+ wop.unobserve(object, key)
151
+ wop.clear_empty_targets!
152
+ end
153
+
154
+ def rmext_unobserve_all
155
+ wop = ::RMExtensions::WeakObserverProxy.get(self)
156
+ wop.unobserve_all
157
+ end
158
+
159
+ def rmext_on_dealloc(&block)
160
+ internalObject = ::RMExtensions::OnDeallocInternalObject.create(&block)
161
+ internalObject.obj = self
162
+ @rmext_on_dealloc_blocks ||= {}
163
+ @rmext_on_dealloc_blocks[internalObject] = internalObject
164
+ nil
165
+ end
166
+
167
+ def rmext_cancel_on_dealloc(block)
168
+ @rmext_on_dealloc_blocks ||= {}
169
+ if internalObject = @rmext_on_dealloc_blocks[block]
170
+ internalObject.block = nil
171
+ @rmext_on_dealloc_blocks.delete(block)
172
+ end
173
+ nil
174
+ end
175
+
176
+ end
177
+
178
+ end
179
+ Object.send(:include, ::RMExtensions::ObjectExtensions)
180
+
181
+ module RMExtensions
182
+ # You don't use these classes directly.
183
+ class Context
184
+
185
+ class << self
186
+ def create(origin, &block)
187
+ x = new
188
+ block.call(x) unless block.nil?
189
+ x
190
+ end
191
+ end
192
+
193
+ attr_accessor :hash
194
+
195
+ def initialize
196
+ self.hash = {}
197
+ end
198
+
199
+ def method_missing(method, *args)
200
+ m = method.to_s
201
+ if m =~ /(.+)?=$/
202
+ hash[$1] = args.first
203
+ else
204
+ hash[m]
205
+ end
107
206
  end
108
207
 
109
208
  end
110
209
 
111
- # You don't use this class directly.
112
- class RetainedContext
210
+ class RetainedContext < Context
113
211
 
114
212
  class << self
115
213
  def rmext_retains
@@ -122,31 +220,25 @@ module RMExtensions
122
220
  @rmext_retains_queue
123
221
  end
124
222
 
125
- def retained(origin, &block)
223
+ def create(origin, &block)
126
224
  x = new
127
225
  x.hash["retained_origin"] = origin
128
226
  x.hash["retained_block"] = block
129
227
  x.rmext_retain!
130
- block.call(x)
228
+ block.call(x) unless block.nil?
131
229
  x
132
230
  end
133
231
  end
134
232
 
135
- attr_accessor :hash
136
-
137
- def initialize
138
- self.hash = {}
139
- end
140
-
141
233
  # if you provide a block, you are responsible for calling #detach!,
142
234
  # otherwise, the expiration handler will just call #detach!
143
- def beginBackground!(&block)
235
+ def begin_background!(&block)
144
236
  hash["bgTaskExpirationHandler"] = block
145
237
  hash["bgTask"] = UIApplication.sharedApplication.beginBackgroundTaskWithExpirationHandler(-> do
146
238
  if hash["bgTaskExpirationHandler"]
147
239
  hash["bgTaskExpirationHandler"].call
148
240
  else
149
- x.detach!
241
+ detach!
150
242
  end
151
243
  end)
152
244
  end
@@ -159,19 +251,85 @@ module RMExtensions
159
251
  rmext_detach!
160
252
  end
161
253
 
254
+ def detach_on_death_of(object)
255
+ object.rmext_on_dealloc(&detach_death_proc)
256
+ end
257
+
258
+ def detach_death_proc
259
+ proc { |x| detach! }
260
+ end
261
+
162
262
  def method_missing(method, *args)
163
263
  unless hash
164
264
  raise "You detached this rmext_retained_context and then called: #{method}"
165
265
  end
166
- m = method.to_s
167
- if m =~ /(.+)?=$/
168
- hash[$1] = args.first
169
- else
170
- hash[m]
266
+ super
267
+ end
268
+
269
+ end
270
+
271
+ class WeakObserverProxy
272
+ include BW::KVO
273
+ rmext_weak_attr_accessor :obj
274
+ attr_accessor :strong_object_id, :strong_class_name
275
+ def initialize(strong_object)
276
+ self.obj = strong_object
277
+ self.strong_object_id = strong_object.object_id
278
+ self.strong_class_name = strong_object.class.name
279
+ self.class.weak_observer_map[strong_object_id] = self
280
+ strong_object.rmext_on_dealloc(&kill_observation_proc)
281
+ end
282
+ # isolate this in its own method so it wont create a retain cycle
283
+ def kill_observation_proc
284
+ proc { |x|
285
+ # uncomment to verify deallocation is working. if not, there is probably
286
+ # a retain cycle somewhere in your code.
287
+ # p "kill_observation_proc", self
288
+ self.obj = nil
289
+ unobserve_all
290
+ self.class.weak_observer_map.delete(strong_object_id)
291
+ }
292
+ end
293
+ def clear_empty_targets!
294
+ return if @targets.nil?
295
+ @targets.each_pair do |target, key_paths|
296
+ if !key_paths || key_paths.size == 0
297
+ @targets.delete(target)
298
+ end
171
299
  end
300
+ nil
301
+ end
302
+ def inspect
303
+ "#{strong_class_name}:#{strong_object_id}"
304
+ end
305
+ def targets
306
+ @targets
307
+ end
308
+ def self.weak_observer_map
309
+ Dispatch.once { $weak_observer_map = {} }
310
+ $weak_observer_map
311
+ end
312
+ def self.get(obj)
313
+ return obj if obj.is_a?(WeakObserverProxy)
314
+ weak_observer_map[obj.object_id] || new(obj)
172
315
  end
316
+ end
173
317
 
318
+ class OnDeallocInternalObject
319
+ attr_accessor :block
320
+ rmext_weak_attr_accessor :obj
321
+ def self.create(&block)
322
+ x = new
323
+ x.block = block
324
+ x
325
+ end
326
+ def dealloc
327
+ if block
328
+ block.call(obj)
329
+ self.block = nil
330
+ end
331
+ super
332
+ end
174
333
  end
175
334
 
176
335
  end
177
- Object.send(:include, ::RMExtensions::ObjectExtensions)
@@ -1,3 +1,3 @@
1
1
  module RMExtensions
2
- VERSION = "0.0.1"
2
+ VERSION = "0.0.2"
3
3
  end
@@ -16,4 +16,5 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+ gem.add_dependency('bubble-wrap', '>= 1.1.5')
19
20
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rm-extensions
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.0.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,24 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-04-19 00:00:00.000000000 Z
13
- dependencies: []
12
+ date: 2013-05-01 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bubble-wrap
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.1.5
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.1.5
14
30
  description: Extensions and helpers for dealing with various areas of rubymotion
15
31
  email:
16
32
  - joenoon@gmail.com