rm-extensions 0.0.1 → 0.0.2

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