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.
- data/lib/motion/rm-extensions.rb +178 -20
- data/lib/rm-extensions/version.rb +1 -1
- data/rm-extensions.gemspec +1 -0
- metadata +19 -3
data/lib/motion/rm-extensions.rb
CHANGED
@@ -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 #
|
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.
|
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
|
-
|
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
|
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
|
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
|
-
|
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
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
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)
|
data/rm-extensions.gemspec
CHANGED
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.
|
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-
|
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
|