rm-extensions 0.0.5 → 0.0.6
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.
- data/README.md +239 -18
- data/lib/motion/accessors.rb +44 -0
- data/lib/motion/context.rb +118 -0
- data/lib/motion/deallocation.rb +56 -0
- data/lib/motion/observation.rb +97 -0
- data/lib/motion/queues.rb +68 -0
- data/lib/motion/retention.rb +44 -0
- data/lib/motion/util.rb +18 -0
- data/lib/rm-extensions.rb +10 -2
- data/lib/rm-extensions/version.rb +1 -1
- metadata +9 -3
- data/lib/motion/rm-extensions.rb +0 -342
data/README.md
CHANGED
@@ -1,36 +1,246 @@
|
|
1
|
-
|
1
|
+
RMExtensions
|
2
|
+
-----------------
|
2
3
|
|
3
|
-
Extensions and helpers for dealing with various areas of rubymotion
|
4
|
+
#### Extensions and helpers for dealing with various areas of rubymotion.
|
4
5
|
|
5
|
-
|
6
|
-
- block/scope/local variable issues
|
7
|
-
- GCD blocks
|
8
|
-
- retaining objects through async procedures
|
9
|
-
- weak attr_accessors
|
6
|
+
## Observation
|
10
7
|
|
11
|
-
|
8
|
+
#### Make observations without needing to clean up/unobserve, and avoid retain-cycles
|
12
9
|
|
13
|
-
|
10
|
+
Call from anywhere on anything without prior inclusion of BW::KVO:
|
14
11
|
|
15
|
-
|
12
|
+
```ruby
|
13
|
+
class MyViewController < UIViewController
|
14
|
+
def viewDidLoad
|
15
|
+
super.tap do
|
16
|
+
rmext_observe(@model, "name") do |val|
|
17
|
+
p "name is #{val}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
```
|
16
23
|
|
17
|
-
|
24
|
+
Under the hood this piggy-backs on Bubblewrap's KVO implementation.
|
18
25
|
|
19
|
-
|
26
|
+
Differences:
|
27
|
+
|
28
|
+
- No need to include BW::KVO anywhere
|
29
|
+
- The default is to observe and immediately fire the supplied callback
|
30
|
+
- The callback only takes one argument, the new value
|
31
|
+
- the object observing is not retained, and when it is deallocated, the observation
|
32
|
+
will be removed automatically for you. there is typically no need to clean up
|
33
|
+
and unobserve in viewWillDisappear, or similar.
|
34
|
+
- because the observation actually happens on an unretained proxy object, the real
|
35
|
+
object shouldnt incur any retain cycles.
|
36
|
+
|
37
|
+
Similarities:
|
38
|
+
|
39
|
+
- the object observed is retained
|
40
|
+
|
41
|
+
|
42
|
+
## Accessors
|
43
|
+
|
44
|
+
#### weak attr_accessors when you need to avoid retain-cycles:
|
45
|
+
|
46
|
+
```ruby
|
47
|
+
|
48
|
+
class MyView < UIView
|
49
|
+
rmext_weak_attr_accessor :delegate
|
50
|
+
end
|
51
|
+
|
52
|
+
class MyViewController < UIViewController
|
53
|
+
def viewDidLoad
|
54
|
+
super.tap do
|
55
|
+
v = MyView.alloc.initWithFrame(CGRectZero)
|
56
|
+
view.addSubview(v)
|
57
|
+
# if delegate was a normal attr_accessor, this controller could never be deallocated
|
58
|
+
v.delegate = self
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
```
|
64
|
+
|
65
|
+
## Deallocation
|
66
|
+
|
67
|
+
#### watch for an object to deallocate, and execute a callback:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
def add_view_controller
|
71
|
+
controller = UIViewController.alloc.init
|
72
|
+
controller.rmext_on_dealloc(&test_dealloc_proc)
|
73
|
+
navigationController.pushViewController(controller, animated: true)
|
74
|
+
end
|
75
|
+
|
76
|
+
def test_dealloc_proc
|
77
|
+
proc { |x| p "it deallocated!" }
|
78
|
+
end
|
79
|
+
|
80
|
+
# now you can verify the controller gets deallocated by calling #add_view_controller
|
81
|
+
# and then popping it off the navigationController
|
82
|
+
|
83
|
+
# you should be careful not to create the block inline, since it could easily create a retain cycle
|
84
|
+
# depending what other objects are in scope.
|
85
|
+
```
|
86
|
+
## Queues
|
87
|
+
|
88
|
+
#### Wraps GCD to avoid complier issues with blocks and also ensures the block passed is retained until executed on the queue:
|
89
|
+
|
90
|
+
```ruby
|
91
|
+
# note +i+ will appear in order, and the thread will never change (main)
|
92
|
+
100.times do |i|
|
93
|
+
rmext_on_main_q do
|
94
|
+
p "i: #{i} thread: #{NSThread.currentThread}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# note +i+ will appear in order, and the thread will change
|
99
|
+
100.times do |i|
|
100
|
+
rmext_on_serial_q("testing") do
|
101
|
+
p "i: #{i} thread: #{NSThread.currentThread}"
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# note +i+ will sometimes appear out of order, and the thread will change
|
106
|
+
100.times do |i|
|
107
|
+
rmext_on_concurrent_q("testing") do
|
108
|
+
p "i: #{i} thread: #{NSThread.currentThread}"
|
109
|
+
end
|
110
|
+
end
|
111
|
+
```
|
112
|
+
|
113
|
+
## Context
|
114
|
+
|
115
|
+
#### break through local variable scope bugs, where using instance variables would mean your method is not re-entrant. retain objects through asynchronous operations.
|
116
|
+
|
117
|
+
##### rmext_context
|
118
|
+
|
119
|
+
```ruby
|
120
|
+
# yields an object you can treat like an openstruct. you can get/set any property
|
121
|
+
# on it. useful for scope issues where local variables wont work, and where instance
|
122
|
+
# variables would clutter the object and not be re-entrant.
|
123
|
+
|
124
|
+
# Consider this example:
|
125
|
+
|
126
|
+
button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
|
127
|
+
button.when_tapped do
|
128
|
+
button.setTitle("Tapped", forState:UIControlStateNormal)
|
129
|
+
end
|
130
|
+
view.addSubview(button)
|
20
131
|
|
21
|
-
|
132
|
+
# when button is tapped, you will get this:
|
133
|
+
# >> Program received signal EXC_BAD_ACCESS, Could not access memory.
|
22
134
|
|
23
|
-
|
135
|
+
# Workaround using +rmext_context+:
|
136
|
+
|
137
|
+
rmext_context do |x|
|
138
|
+
x.button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
|
139
|
+
x.button.when_tapped do
|
140
|
+
x.button.setTitle("Tapped", forState:UIControlStateNormal)
|
141
|
+
end
|
142
|
+
view.addSubview(x.button)
|
143
|
+
end
|
144
|
+
|
145
|
+
# when button is tapped, it works.
|
146
|
+
|
147
|
+
# a note about the different use cases for +rmext_context+ and +rmext_retained_context+,
|
148
|
+
# because its important to understand when to use which, and what different purposes they
|
149
|
+
# are for:
|
150
|
+
|
151
|
+
# +rmext_context+ is used here instead of +rmext_retained_context+ because:
|
152
|
+
|
153
|
+
# 1. the button is already going to be retained by the view its added to, so
|
154
|
+
# there is no need for us to retain it explicitly.
|
155
|
+
# 2. there would be no clear way to eventually "detach" it, since the button
|
156
|
+
# could be clicked any number of times.
|
157
|
+
```
|
158
|
+
|
159
|
+
##### rmext_retained_context
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
# like +rmext_context+ but the context is retained (as well as anything set on it) until you
|
163
|
+
# explicitly call +detach!+ or +detach_on_death_of+ and that object is deallocated. prevents
|
164
|
+
# deallocation of objects until you are done with them, for example through asynchronous
|
165
|
+
# operations.
|
166
|
+
|
167
|
+
# also has a useful shortcut for beginBackgroundTaskWithExpirationHandler/endBackgroundTask
|
168
|
+
# via +begin_background!+. when you call +detach!+ the background task will be ended for you
|
169
|
+
# as well.
|
170
|
+
|
171
|
+
# use this over +rmext_context+ when you have a scenario when eventually you know everything
|
172
|
+
# is complete, and can call +detach!+. for example, an operation that makes an http request,
|
173
|
+
# uses the result to call another operation on a specific queue, and is finally considered
|
174
|
+
# "finished" at some point in time in the future. there is a definitive "end", at some point
|
175
|
+
# in the future.
|
176
|
+
|
177
|
+
# example:
|
178
|
+
|
179
|
+
rmext_retained_context do |x|
|
180
|
+
rmext_on_serial_q("my_serial_q") do
|
181
|
+
some_async_http_request do |results1|
|
182
|
+
x.results1 = results1
|
183
|
+
rmext_on_serial_q("my_serial_q") do
|
184
|
+
some_other_async_http_request do |results2|
|
185
|
+
x.results2 = results2
|
186
|
+
rmext_on_main_q do
|
187
|
+
p "results1", x.results1
|
188
|
+
p "results2", x.results2
|
189
|
+
x.detach!
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
```
|
197
|
+
|
198
|
+
## Retention
|
199
|
+
|
200
|
+
#### A type of retain/release that just uses rubymotion's memory-management rules instead of calling the native retain/release:
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
class MyViewController < UITableViewController
|
204
|
+
|
205
|
+
# note here, if the view controller is deallocated during the http request (someone hits Back, etc),
|
206
|
+
# and then the http request finishes, and you try to call tableView.reloadData, it will be a
|
207
|
+
# EXC_BAD_ACCESS:
|
208
|
+
def fetch_unsafe
|
209
|
+
remote_http_request do |result|
|
210
|
+
@models = []
|
211
|
+
tableView.reloadData
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# ensure self stay around long enough for the block to be called
|
216
|
+
def fetch
|
217
|
+
rmext_retain!
|
218
|
+
remote_http_request do |result|
|
219
|
+
@models = []
|
220
|
+
tableView.reloadData
|
221
|
+
rmext_detach!
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
end
|
226
|
+
```
|
227
|
+
|
228
|
+
Installation
|
229
|
+
-----------------
|
230
|
+
|
231
|
+
Add this line to your application's Gemfile:
|
232
|
+
|
233
|
+
gem 'rm-extensions'
|
24
234
|
|
25
235
|
And then execute:
|
26
236
|
|
27
237
|
$ bundle
|
28
238
|
|
29
|
-
|
239
|
+
* Currently depends on bubblewrap (for BW::KVO).
|
240
|
+
* AssociatedObject objc runtime taken from BlocksKit, modified to work with rubymotion.
|
30
241
|
|
31
|
-
|
32
|
-
|
33
|
-
## Contributing
|
242
|
+
Contributing
|
243
|
+
-----------------
|
34
244
|
|
35
245
|
If you have a better way to accomplish anything this library is doing, please share!
|
36
246
|
|
@@ -39,3 +249,14 @@ If you have a better way to accomplish anything this library is doing, please sh
|
|
39
249
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
40
250
|
4. Push to the branch (`git push origin my-new-feature`)
|
41
251
|
5. Create new Pull Request
|
252
|
+
|
253
|
+
License
|
254
|
+
-----------------
|
255
|
+
|
256
|
+
Please see [LICENSE](https://github.com/joenoon/rm-extensions/blob/master/LICENSE.txt) for licensing details.
|
257
|
+
|
258
|
+
|
259
|
+
Author
|
260
|
+
-----------------
|
261
|
+
|
262
|
+
Joe Noon, [joenoon](https://github.com/joenoon)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
module ObjectExtensions
|
4
|
+
|
5
|
+
module Accessors
|
6
|
+
|
7
|
+
# creates an +attr_accessor+ like behavior, but the objects are stored with
|
8
|
+
# a weak reference (OBJC_ASSOCIATION_ASSIGN). useful to avoid retain cycles
|
9
|
+
# when you want to have access to an object in a place that isnt responsible
|
10
|
+
# for that object's lifecycle.
|
11
|
+
# does not conform to KVO like attr_accessor does.
|
12
|
+
def rmext_weak_attr_accessor(*attrs)
|
13
|
+
attrs.each do |attr|
|
14
|
+
define_method(attr) do
|
15
|
+
rmext_associatedValueForKey(attr.to_sym)
|
16
|
+
end
|
17
|
+
define_method("#{attr}=") do |val|
|
18
|
+
rmext_weaklyAssociateValue(val, withKey: attr.to_sym)
|
19
|
+
val
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
# creates an +attr_accessor+ like behavior, but the objects are stored with
|
25
|
+
# OBJC_ASSOCIATION_COPY.
|
26
|
+
# does not conform to KVO like attr_accessor does.
|
27
|
+
def rmext_copy_attr_accessor(*attrs)
|
28
|
+
attrs.each do |attr|
|
29
|
+
define_method(attr) do
|
30
|
+
rmext_associatedValueForKey(attr.to_sym)
|
31
|
+
end
|
32
|
+
define_method("#{attr}=") do |val|
|
33
|
+
rmext_atomicallyAssociateCopyOfValue(val, withKey: attr.to_sym)
|
34
|
+
val
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Accessors)
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
module ObjectExtensions
|
4
|
+
|
5
|
+
module Context
|
6
|
+
|
7
|
+
# yields an object you can treat like an openstruct. you can get/set any property
|
8
|
+
# on it. useful for scope issues where local variables wont work, and where instance
|
9
|
+
# variables would clutter the object and not be re-entrant.
|
10
|
+
def rmext_context(&block)
|
11
|
+
::RMExtensions::Context.create(self, &block)
|
12
|
+
end
|
13
|
+
|
14
|
+
# like +rmext_context+ but the context is retained (as well as anything set on it) until you
|
15
|
+
# explicitly call +detach!+ or +detach_on_death_of+ and that object is deallocated. prevents
|
16
|
+
# deallocation of objects until you are done with them, for example through asynchronous
|
17
|
+
# operations.
|
18
|
+
#
|
19
|
+
# also has a useful shortcut for beginBackgroundTaskWithExpirationHandler/endBackgroundTask
|
20
|
+
# via +begin_background!+. when you call +detach!+ the background task will be ended for you
|
21
|
+
# as well.
|
22
|
+
#
|
23
|
+
# use this over +rmext_context+ when you have a scenario when eventually you know everything
|
24
|
+
# is complete, and can call +detach!+. for example, an operation that makes an http request,
|
25
|
+
# uses the result to call another operation on a specific queue, and is finally considered
|
26
|
+
# "finished" at some point in time in the future. there is a definitive "end", at some point
|
27
|
+
# in the future.
|
28
|
+
def rmext_retained_context(&block)
|
29
|
+
::RMExtensions::RetainedContext.create(self, &block)
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
class Context
|
37
|
+
|
38
|
+
class << self
|
39
|
+
def create(origin, &block)
|
40
|
+
x = new
|
41
|
+
block.call(x) unless block.nil?
|
42
|
+
x
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
attr_accessor :hash
|
47
|
+
|
48
|
+
def initialize
|
49
|
+
self.hash = {}
|
50
|
+
end
|
51
|
+
|
52
|
+
def method_missing(method, *args)
|
53
|
+
m = method.to_s
|
54
|
+
if m =~ /(.+)?=$/
|
55
|
+
hash[$1] = args.first
|
56
|
+
else
|
57
|
+
hash[m]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
class RetainedContext < Context
|
64
|
+
|
65
|
+
class << self
|
66
|
+
def create(origin, &block)
|
67
|
+
x = new
|
68
|
+
# automatically retain the origin and block
|
69
|
+
x.hash["retained_origin"] = origin
|
70
|
+
x.hash["retained_block"] = block
|
71
|
+
x.rmext_retain!
|
72
|
+
block.call(x) unless block.nil?
|
73
|
+
x
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# if you provide a block, you are responsible for calling #detach!,
|
78
|
+
# otherwise, the expiration handler will just call #detach!
|
79
|
+
def begin_background!(&block)
|
80
|
+
hash["bgTaskExpirationHandler"] = block
|
81
|
+
hash["bgTask"] = UIApplication.sharedApplication.beginBackgroundTaskWithExpirationHandler(-> do
|
82
|
+
if hash["bgTaskExpirationHandler"]
|
83
|
+
hash["bgTaskExpirationHandler"].call
|
84
|
+
else
|
85
|
+
detach!
|
86
|
+
end
|
87
|
+
end)
|
88
|
+
end
|
89
|
+
|
90
|
+
def detach!
|
91
|
+
# end the bgTask if one was created
|
92
|
+
if hash["bgTask"] && hash["bgTask"] != UIBackgroundTaskInvalid
|
93
|
+
UIApplication.sharedApplication.endBackgroundTask(hash["bgTask"])
|
94
|
+
end
|
95
|
+
self.hash = nil
|
96
|
+
rmext_detach!
|
97
|
+
end
|
98
|
+
|
99
|
+
# watch some other object for deallocation, and when it does, +detach!+ self
|
100
|
+
def detach_on_death_of(object)
|
101
|
+
object.rmext_on_dealloc(&detach_death_proc)
|
102
|
+
end
|
103
|
+
|
104
|
+
def detach_death_proc
|
105
|
+
proc { |x| detach! }
|
106
|
+
end
|
107
|
+
|
108
|
+
def method_missing(method, *args)
|
109
|
+
unless hash
|
110
|
+
raise "You detached this rmext_retained_context and then called: #{method}"
|
111
|
+
end
|
112
|
+
super
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Context)
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
module ObjectExtensions
|
4
|
+
|
5
|
+
module Deallocation
|
6
|
+
|
7
|
+
# perform a block before +self+ will dealloc.
|
8
|
+
# the block given should have one argument, the object about to be deallocated.
|
9
|
+
def rmext_on_dealloc(&block)
|
10
|
+
internalObject = ::RMExtensions::OnDeallocInternalObject.create("#{self.class.name}:#{object_id}", self, block)
|
11
|
+
@rmext_on_dealloc_blocks ||= {}
|
12
|
+
@rmext_on_dealloc_blocks[internalObject] = internalObject
|
13
|
+
nil
|
14
|
+
end
|
15
|
+
|
16
|
+
# removes a previously added block from the deallocation callback list
|
17
|
+
def rmext_cancel_on_dealloc(block)
|
18
|
+
@rmext_on_dealloc_blocks ||= {}
|
19
|
+
if internalObject = @rmext_on_dealloc_blocks[block]
|
20
|
+
internalObject.block = nil
|
21
|
+
@rmext_on_dealloc_blocks.delete(block)
|
22
|
+
end
|
23
|
+
nil
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
# Used internally by +rmext_on_dealloc+. The idea is this object is added to the
|
31
|
+
# object we want to watch for deallocation. When the object we want to watch
|
32
|
+
# is about to dealloc, this object will dealloc first, so we can execute the block.
|
33
|
+
# the object it follows is kept only as a weak reference to not create
|
34
|
+
# a retain cycle.
|
35
|
+
class OnDeallocInternalObject
|
36
|
+
attr_accessor :description, :block
|
37
|
+
rmext_weak_attr_accessor :obj
|
38
|
+
def self.create(description, obj, block)
|
39
|
+
x = new
|
40
|
+
x.description = description
|
41
|
+
x.obj = obj
|
42
|
+
x.block = block
|
43
|
+
x
|
44
|
+
end
|
45
|
+
def dealloc
|
46
|
+
# p "dealloc OnDeallocInternalObject #{description}"
|
47
|
+
if block
|
48
|
+
block.call(obj)
|
49
|
+
self.block = nil
|
50
|
+
end
|
51
|
+
super
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Deallocation)
|
@@ -0,0 +1,97 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
module ObjectExtensions
|
4
|
+
|
5
|
+
module Observation
|
6
|
+
|
7
|
+
# observe an object.key. takes a block that will be called with the
|
8
|
+
# new value upon change.
|
9
|
+
#
|
10
|
+
# rmext_observe_passive(@model, "name") do |val|
|
11
|
+
# p "name is #{val}"
|
12
|
+
# end
|
13
|
+
def rmext_observe_passive(object, key, &block)
|
14
|
+
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
15
|
+
b = -> (old_value, new_value) do
|
16
|
+
block.call(new_value) unless block.nil?
|
17
|
+
end
|
18
|
+
wop.observe(object, key, &b)
|
19
|
+
end
|
20
|
+
|
21
|
+
# like +rmext_observe_passive+ but additionally fires the callback immediately.
|
22
|
+
def rmext_observe(object, key, &block)
|
23
|
+
# p "+ rmext_observe", self, object, key
|
24
|
+
rmext_observe_passive(object, key, &block)
|
25
|
+
block.call(object.send(key)) unless block.nil?
|
26
|
+
end
|
27
|
+
|
28
|
+
# unobserve an existing observation
|
29
|
+
def rmext_unobserve(object, key)
|
30
|
+
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
31
|
+
wop.unobserve(object, key)
|
32
|
+
wop.clear_empty_targets!
|
33
|
+
end
|
34
|
+
|
35
|
+
# unobserve all existing observations
|
36
|
+
def rmext_unobserve_all
|
37
|
+
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
38
|
+
wop.unobserve_all
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# Proxy class used to hold the actual observation and watches for the real
|
46
|
+
# class intended to hold the observation to be deallocated, so the
|
47
|
+
# observation can be cleaned up.
|
48
|
+
class WeakObserverProxy
|
49
|
+
include BW::KVO
|
50
|
+
rmext_weak_attr_accessor :obj
|
51
|
+
attr_accessor :strong_object_id, :strong_class_name
|
52
|
+
def initialize(strong_object)
|
53
|
+
self.obj = strong_object
|
54
|
+
self.strong_object_id = strong_object.object_id
|
55
|
+
self.strong_class_name = strong_object.class.name
|
56
|
+
self.class.weak_observer_map[strong_object_id] = self
|
57
|
+
strong_object.rmext_on_dealloc(&kill_observation_proc)
|
58
|
+
end
|
59
|
+
# isolate this in its own method so it wont create a retain cycle
|
60
|
+
def kill_observation_proc
|
61
|
+
proc { |x|
|
62
|
+
# uncomment to verify deallocation is working. if not, there is probably
|
63
|
+
# a retain cycle somewhere in your code.
|
64
|
+
# p "kill_observation_proc", self
|
65
|
+
self.obj = nil
|
66
|
+
unobserve_all
|
67
|
+
self.class.weak_observer_map.delete(strong_object_id)
|
68
|
+
}
|
69
|
+
end
|
70
|
+
# get rid of targets that dont contain anything to avoid retain cycles.
|
71
|
+
def clear_empty_targets!
|
72
|
+
return if @targets.nil?
|
73
|
+
@targets.each_pair do |target, key_paths|
|
74
|
+
if !key_paths || key_paths.size == 0
|
75
|
+
@targets.delete(target)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
nil
|
79
|
+
end
|
80
|
+
def inspect
|
81
|
+
"#{strong_class_name}:#{strong_object_id}"
|
82
|
+
end
|
83
|
+
def targets
|
84
|
+
@targets
|
85
|
+
end
|
86
|
+
def self.weak_observer_map
|
87
|
+
Dispatch.once { @weak_observer_map = {} }
|
88
|
+
@weak_observer_map
|
89
|
+
end
|
90
|
+
def self.get(obj)
|
91
|
+
return obj if obj.is_a?(WeakObserverProxy)
|
92
|
+
weak_observer_map[obj.object_id] || new(obj)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Observation)
|
@@ -0,0 +1,68 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
# A hash used by +rmext_on_serial_q+ storing created serial queues, so they
|
4
|
+
# are not instantiated each time they are used.
|
5
|
+
def self.serial_qs
|
6
|
+
Dispatch.once { @serial_qs = {} }
|
7
|
+
@serial_qs
|
8
|
+
end
|
9
|
+
|
10
|
+
module ObjectExtensions
|
11
|
+
|
12
|
+
# Wrapper methods to work around some bugs with GCD and blocks and how the compiler
|
13
|
+
# handles them. See here for more information:
|
14
|
+
#
|
15
|
+
# https://gist.github.com/mattetti/2951773
|
16
|
+
# https://github.com/MacRuby/MacRuby/issues/152
|
17
|
+
# blocks within blocks can be a problem with GCD (and maybe RM/MacRuby in general?).
|
18
|
+
# these helpers make it easy to use nested blocks with GCD, and also ensures those
|
19
|
+
# blocks will not be garbage collected until at least after they have been called.
|
20
|
+
#
|
21
|
+
# Also has the added benefit of ensuring your block is retained at least until
|
22
|
+
# it's been executed on the queue used.
|
23
|
+
#
|
24
|
+
# These helper methods are all for async mode.
|
25
|
+
module Queues
|
26
|
+
|
27
|
+
# execute a block on the main queue, asynchronously.
|
28
|
+
def rmext_on_main_q(&block)
|
29
|
+
rmext_retained_context do |x|
|
30
|
+
x.block = -> do
|
31
|
+
block.call
|
32
|
+
x.detach!
|
33
|
+
end
|
34
|
+
Dispatch::Queue.main.async(&x.block)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# execute a block on a serial queue, asynchronously.
|
39
|
+
def rmext_on_serial_q(q, &block)
|
40
|
+
rmext_retained_context do |x|
|
41
|
+
x.block = -> do
|
42
|
+
block.call
|
43
|
+
x.detach!
|
44
|
+
end
|
45
|
+
x.key = "#{NSBundle.mainBundle.bundleIdentifier}.serial.#{q}"
|
46
|
+
::RMExtensions.serial_qs[x.key] ||= Dispatch::Queue.new(x.key)
|
47
|
+
::RMExtensions.serial_qs[x.key].async(&x.block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# execute a block on a concurrent queue, asynchronously.
|
52
|
+
def rmext_on_concurrent_q(q, &block)
|
53
|
+
rmext_retained_context do |x|
|
54
|
+
x.block = -> do
|
55
|
+
block.call
|
56
|
+
x.detach!
|
57
|
+
end
|
58
|
+
x.key = "#{NSBundle.mainBundle.bundleIdentifier}.concurrent.#{q}"
|
59
|
+
Dispatch::Queue.concurrent(x.key).async(&x.block)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Queues)
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
# A retained array, which will hold other objects we want retained.
|
4
|
+
def self.retained_items
|
5
|
+
Dispatch.once { @retained_items = [] }
|
6
|
+
@retained_items
|
7
|
+
end
|
8
|
+
|
9
|
+
# A serial queue to perform all retain/detach operations on, to ensure we are always modifying
|
10
|
+
# +retained_items+ on the same thread.
|
11
|
+
def self.retains_queue
|
12
|
+
Dispatch.once { @retains_queue = Dispatch::Queue.new("#{NSBundle.mainBundle.bundleIdentifier}.rmext_retains_queue") }
|
13
|
+
@retains_queue
|
14
|
+
end
|
15
|
+
|
16
|
+
module ObjectExtensions
|
17
|
+
|
18
|
+
module Retention
|
19
|
+
|
20
|
+
# adds +self+ to +retained_items+. this ensures +self+ will be retained at least
|
21
|
+
# until +self+ is removed from +retained_items+ by calling +rmext_detach!+
|
22
|
+
def rmext_retain!
|
23
|
+
::RMExtensions.retains_queue.sync do
|
24
|
+
::RMExtensions.retained_items.push(self)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# removes one instance of +self+ from +retained_items+. if +rmext_retain!+ has been called
|
29
|
+
# multiple times on an object, +rmext_detach!+ must be called an equal number of times for
|
30
|
+
# it to be completely removed from +retained_items+. even after the object is completely
|
31
|
+
# out of +retained_items+, it may still be retained in memory if there are strong references
|
32
|
+
# to it anywhere else in your code.
|
33
|
+
def rmext_detach!
|
34
|
+
::RMExtensions.retains_queue.async do
|
35
|
+
::RMExtensions.retained_items.delete_at(::RMExtensions.retained_items.index(self) || ::RMExtensions.retained_items.length)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Retention)
|
data/lib/motion/util.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
module RMExtensions
|
2
|
+
|
3
|
+
module ObjectExtensions
|
4
|
+
|
5
|
+
module Util
|
6
|
+
|
7
|
+
# Raises an exception when called from a thread other than the main thread.
|
8
|
+
# Good for development and experimenting.
|
9
|
+
def rmext_assert_main_thread!
|
10
|
+
raise "This method must be called on the main thread." unless NSThread.currentThread.isMainThread
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
Object.send(:include, ::RMExtensions::ObjectExtensions::Util)
|
data/lib/rm-extensions.rb
CHANGED
@@ -5,8 +5,16 @@ unless defined?(Motion::Project::Config)
|
|
5
5
|
end
|
6
6
|
|
7
7
|
Motion::Project::App.setup do |app|
|
8
|
-
|
9
|
-
|
8
|
+
%w(
|
9
|
+
util
|
10
|
+
retention
|
11
|
+
accessors
|
12
|
+
deallocation
|
13
|
+
context
|
14
|
+
observation
|
15
|
+
queues
|
16
|
+
).reverse.each do |x|
|
17
|
+
app.files.unshift(File.join(File.dirname(__FILE__), "motion/#{x}.rb"))
|
10
18
|
end
|
11
19
|
app.vendor_project(File.join(File.dirname(__FILE__), '../ext'), :static)
|
12
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.
|
4
|
+
version: 0.0.6
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05-
|
12
|
+
date: 2013-05-04 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: bubble-wrap
|
@@ -41,7 +41,13 @@ files:
|
|
41
41
|
- Rakefile
|
42
42
|
- ext/NSObject+RMExtensions.h
|
43
43
|
- ext/NSObject+RMExtensions.m
|
44
|
-
- lib/motion/
|
44
|
+
- lib/motion/accessors.rb
|
45
|
+
- lib/motion/context.rb
|
46
|
+
- lib/motion/deallocation.rb
|
47
|
+
- lib/motion/observation.rb
|
48
|
+
- lib/motion/queues.rb
|
49
|
+
- lib/motion/retention.rb
|
50
|
+
- lib/motion/util.rb
|
45
51
|
- lib/rm-extensions.rb
|
46
52
|
- lib/rm-extensions/version.rb
|
47
53
|
- rm-extensions.gemspec
|
data/lib/motion/rm-extensions.rb
DELETED
@@ -1,342 +0,0 @@
|
|
1
|
-
module RMExtensions
|
2
|
-
|
3
|
-
# this module is included on Object, so these methods are available from anywhere in your code.
|
4
|
-
module ObjectExtensions
|
5
|
-
|
6
|
-
def rmext_weak_attr_accessor(*attrs)
|
7
|
-
attrs.each do |attr|
|
8
|
-
define_method(attr) do
|
9
|
-
rmext_associatedValueForKey(attr.to_sym)
|
10
|
-
end
|
11
|
-
define_method("#{attr}=") do |val|
|
12
|
-
rmext_weaklyAssociateValue(val, withKey: attr.to_sym)
|
13
|
-
val
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
|
18
|
-
def rmext_copy_attr_accessor(*attrs)
|
19
|
-
attrs.each do |attr|
|
20
|
-
define_method(attr) do
|
21
|
-
rmext_associatedValueForKey(attr.to_sym)
|
22
|
-
end
|
23
|
-
define_method("#{attr}=") do |val|
|
24
|
-
rmext_associateCopyOfValue(val, withKey: attr.to_sym)
|
25
|
-
val
|
26
|
-
end
|
27
|
-
end
|
28
|
-
end
|
29
|
-
|
30
|
-
def rmext_assert_main_thread!
|
31
|
-
raise "This method must be called on the main thread." unless NSThread.currentThread.isMainThread
|
32
|
-
end
|
33
|
-
|
34
|
-
# https://gist.github.com/mattetti/2951773
|
35
|
-
# https://github.com/MacRuby/MacRuby/issues/152
|
36
|
-
# blocks within blocks can be a problem with GCD (and maybe RM/MacRuby in general?).
|
37
|
-
# these helpers make it easy to use nested blocks with GCD, and also ensures those
|
38
|
-
# blocks will not be garbage collected until at least after they have been called.
|
39
|
-
|
40
|
-
def rmext_on_main_q(&block)
|
41
|
-
rmext_retained_context do |x|
|
42
|
-
x.block = -> do
|
43
|
-
block.call
|
44
|
-
x.detach!
|
45
|
-
end
|
46
|
-
Dispatch::Queue.main.async(&x.block)
|
47
|
-
end
|
48
|
-
end
|
49
|
-
|
50
|
-
def rmext_on_serial_q(q, &block)
|
51
|
-
Dispatch.once { $serial_qs = {} }
|
52
|
-
rmext_retained_context do |x|
|
53
|
-
x.block = -> do
|
54
|
-
block.call
|
55
|
-
x.detach!
|
56
|
-
end
|
57
|
-
x.key = "#{NSBundle.mainBundle.bundleIdentifier}.serial.#{q}"
|
58
|
-
$serial_qs[x.key] ||= Dispatch::Queue.new(x.key)
|
59
|
-
$serial_qs[x.key].async(&x.block)
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def rmext_on_concurrent_q(q, &block)
|
64
|
-
rmext_retained_context do |x|
|
65
|
-
x.block = -> do
|
66
|
-
block.call
|
67
|
-
x.detach!
|
68
|
-
end
|
69
|
-
x.key = "#{NSBundle.mainBundle.bundleIdentifier}.concurrent.#{q}"
|
70
|
-
Dispatch::Queue.concurrent(x.key).async(&x.block)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# #rmext_retain! is different than a normal retain. it adds the object(self) to a retained
|
75
|
-
# array, utilizing RM's underlying GC logic
|
76
|
-
#
|
77
|
-
# you most likely want to use #rmext_retained_context and not call this directly
|
78
|
-
#
|
79
|
-
def rmext_retain!
|
80
|
-
::RMExtensions::RetainedContext.rmext_retains_queue.sync do
|
81
|
-
::RMExtensions::RetainedContext.rmext_retains.push(self)
|
82
|
-
end
|
83
|
-
end
|
84
|
-
|
85
|
-
# #rmext_detach! is slightly similar to the concept of "release". it removes the object(self)
|
86
|
-
# from a retained array (only one hit, in case the same object is #rmext_retain!'d multiple times),
|
87
|
-
# utilizing RM's underlying GC logic. if nothing else has a strong reference to the object after
|
88
|
-
# it is detached, it will eventually be handled by RM's GC.
|
89
|
-
#
|
90
|
-
# you most likely want to use #rmext_retained_context and not call this directly
|
91
|
-
#
|
92
|
-
def rmext_detach!
|
93
|
-
::RMExtensions::RetainedContext.rmext_retains_queue.async do
|
94
|
-
::RMExtensions::RetainedContext.rmext_retains.delete_at(::RMExtensions::RetainedContext.rmext_retains.index(self) || ::RMExtensions::RetainedContext.rmext_retains.length)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
|
-
# #rmext_context yields an object you can treat like an openstruct (the "context")
|
99
|
-
def rmext_context(&block)
|
100
|
-
::RMExtensions::Context.create(self, &block)
|
101
|
-
end
|
102
|
-
|
103
|
-
# #rmext_retained_context yields an object you can treat like an openstruct. you can get/set any
|
104
|
-
# property on it. the context is globally retained, until #detach! is called on the context.
|
105
|
-
# this convention should fill the gap where local variables and scope bugs currently occur in RM,
|
106
|
-
# and it also solves the re-entrant problem of using instance variables for retaining purposes.
|
107
|
-
#
|
108
|
-
# always be sure to #detach! the context at the correct place in time.
|
109
|
-
#
|
110
|
-
# example:
|
111
|
-
#
|
112
|
-
# rmext_retained_context do |x|
|
113
|
-
# rmext_on_serial_q("my_serial_q") do
|
114
|
-
# some_async_http_request do |results1|
|
115
|
-
# x.results1 = results1
|
116
|
-
# rmext_on_serial_q("my_serial_q") do
|
117
|
-
# some_other_async_http_request do |results2|
|
118
|
-
# x.results2 = results2
|
119
|
-
# rmext_on_main_q do
|
120
|
-
# p "results1", x.results1
|
121
|
-
# p "results2", x.results2
|
122
|
-
# x.detach!
|
123
|
-
# end
|
124
|
-
# end
|
125
|
-
# end
|
126
|
-
# end
|
127
|
-
# end
|
128
|
-
# end
|
129
|
-
#
|
130
|
-
# experimental feature:
|
131
|
-
#
|
132
|
-
# you can call #begin_background! on the context, and it will check-out a background task identifier,
|
133
|
-
# and automatically end the background task when you call #detach! as normal.
|
134
|
-
def rmext_retained_context(&block)
|
135
|
-
::RMExtensions::RetainedContext.create(self, &block)
|
136
|
-
end
|
137
|
-
|
138
|
-
|
139
|
-
def rmext_observe(object, key, &block)
|
140
|
-
# p "+ rmext_observe", self, object, key
|
141
|
-
rmext_observe_passive(object, key, &block)
|
142
|
-
block.call(object.send(key)) unless block.nil?
|
143
|
-
end
|
144
|
-
|
145
|
-
def rmext_observe_passive(object, key, &block)
|
146
|
-
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
147
|
-
b = -> (old_value, new_value) do
|
148
|
-
block.call(new_value) unless block.nil?
|
149
|
-
end
|
150
|
-
wop.observe(object, key, &b)
|
151
|
-
end
|
152
|
-
|
153
|
-
def rmext_unobserve(object, key)
|
154
|
-
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
155
|
-
wop.unobserve(object, key)
|
156
|
-
wop.clear_empty_targets!
|
157
|
-
end
|
158
|
-
|
159
|
-
def rmext_unobserve_all
|
160
|
-
wop = ::RMExtensions::WeakObserverProxy.get(self)
|
161
|
-
wop.unobserve_all
|
162
|
-
end
|
163
|
-
|
164
|
-
def rmext_on_dealloc(&block)
|
165
|
-
internalObject = ::RMExtensions::OnDeallocInternalObject.create("#{self.class.name}:#{object_id}", self, block)
|
166
|
-
@rmext_on_dealloc_blocks ||= {}
|
167
|
-
@rmext_on_dealloc_blocks[internalObject] = internalObject
|
168
|
-
nil
|
169
|
-
end
|
170
|
-
|
171
|
-
def rmext_cancel_on_dealloc(block)
|
172
|
-
@rmext_on_dealloc_blocks ||= {}
|
173
|
-
if internalObject = @rmext_on_dealloc_blocks[block]
|
174
|
-
internalObject.block = nil
|
175
|
-
@rmext_on_dealloc_blocks.delete(block)
|
176
|
-
end
|
177
|
-
nil
|
178
|
-
end
|
179
|
-
|
180
|
-
end
|
181
|
-
|
182
|
-
end
|
183
|
-
Object.send(:include, ::RMExtensions::ObjectExtensions)
|
184
|
-
|
185
|
-
module RMExtensions
|
186
|
-
# You don't use these classes directly.
|
187
|
-
class Context
|
188
|
-
|
189
|
-
class << self
|
190
|
-
def create(origin, &block)
|
191
|
-
x = new
|
192
|
-
block.call(x) unless block.nil?
|
193
|
-
x
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
attr_accessor :hash
|
198
|
-
|
199
|
-
def initialize
|
200
|
-
self.hash = {}
|
201
|
-
end
|
202
|
-
|
203
|
-
def method_missing(method, *args)
|
204
|
-
m = method.to_s
|
205
|
-
if m =~ /(.+)?=$/
|
206
|
-
hash[$1] = args.first
|
207
|
-
else
|
208
|
-
hash[m]
|
209
|
-
end
|
210
|
-
end
|
211
|
-
|
212
|
-
end
|
213
|
-
|
214
|
-
class RetainedContext < Context
|
215
|
-
|
216
|
-
class << self
|
217
|
-
def rmext_retains
|
218
|
-
Dispatch.once { @rmext_retains = [] }
|
219
|
-
@rmext_retains
|
220
|
-
end
|
221
|
-
|
222
|
-
def rmext_retains_queue
|
223
|
-
Dispatch.once { @rmext_retains_queue = Dispatch::Queue.new("#{NSBundle.mainBundle.bundleIdentifier}.rmext_retains_queue") }
|
224
|
-
@rmext_retains_queue
|
225
|
-
end
|
226
|
-
|
227
|
-
def create(origin, &block)
|
228
|
-
x = new
|
229
|
-
x.hash["retained_origin"] = origin
|
230
|
-
x.hash["retained_block"] = block
|
231
|
-
x.rmext_retain!
|
232
|
-
block.call(x) unless block.nil?
|
233
|
-
x
|
234
|
-
end
|
235
|
-
end
|
236
|
-
|
237
|
-
# if you provide a block, you are responsible for calling #detach!,
|
238
|
-
# otherwise, the expiration handler will just call #detach!
|
239
|
-
def begin_background!(&block)
|
240
|
-
hash["bgTaskExpirationHandler"] = block
|
241
|
-
hash["bgTask"] = UIApplication.sharedApplication.beginBackgroundTaskWithExpirationHandler(-> do
|
242
|
-
if hash["bgTaskExpirationHandler"]
|
243
|
-
hash["bgTaskExpirationHandler"].call
|
244
|
-
else
|
245
|
-
detach!
|
246
|
-
end
|
247
|
-
end)
|
248
|
-
end
|
249
|
-
|
250
|
-
def detach!
|
251
|
-
if hash["bgTask"] && hash["bgTask"] != UIBackgroundTaskInvalid
|
252
|
-
UIApplication.sharedApplication.endBackgroundTask(hash["bgTask"])
|
253
|
-
end
|
254
|
-
self.hash = nil
|
255
|
-
rmext_detach!
|
256
|
-
end
|
257
|
-
|
258
|
-
def detach_on_death_of(object)
|
259
|
-
object.rmext_on_dealloc(&detach_death_proc)
|
260
|
-
end
|
261
|
-
|
262
|
-
def detach_death_proc
|
263
|
-
proc { |x| detach! }
|
264
|
-
end
|
265
|
-
|
266
|
-
def method_missing(method, *args)
|
267
|
-
unless hash
|
268
|
-
raise "You detached this rmext_retained_context and then called: #{method}"
|
269
|
-
end
|
270
|
-
super
|
271
|
-
end
|
272
|
-
|
273
|
-
end
|
274
|
-
|
275
|
-
class WeakObserverProxy
|
276
|
-
include BW::KVO
|
277
|
-
rmext_weak_attr_accessor :obj
|
278
|
-
attr_accessor :strong_object_id, :strong_class_name
|
279
|
-
def initialize(strong_object)
|
280
|
-
self.obj = strong_object
|
281
|
-
self.strong_object_id = strong_object.object_id
|
282
|
-
self.strong_class_name = strong_object.class.name
|
283
|
-
self.class.weak_observer_map[strong_object_id] = self
|
284
|
-
strong_object.rmext_on_dealloc(&kill_observation_proc)
|
285
|
-
end
|
286
|
-
# isolate this in its own method so it wont create a retain cycle
|
287
|
-
def kill_observation_proc
|
288
|
-
proc { |x|
|
289
|
-
# uncomment to verify deallocation is working. if not, there is probably
|
290
|
-
# a retain cycle somewhere in your code.
|
291
|
-
# p "kill_observation_proc", self
|
292
|
-
self.obj = nil
|
293
|
-
unobserve_all
|
294
|
-
self.class.weak_observer_map.delete(strong_object_id)
|
295
|
-
}
|
296
|
-
end
|
297
|
-
def clear_empty_targets!
|
298
|
-
return if @targets.nil?
|
299
|
-
@targets.each_pair do |target, key_paths|
|
300
|
-
if !key_paths || key_paths.size == 0
|
301
|
-
@targets.delete(target)
|
302
|
-
end
|
303
|
-
end
|
304
|
-
nil
|
305
|
-
end
|
306
|
-
def inspect
|
307
|
-
"#{strong_class_name}:#{strong_object_id}"
|
308
|
-
end
|
309
|
-
def targets
|
310
|
-
@targets
|
311
|
-
end
|
312
|
-
def self.weak_observer_map
|
313
|
-
Dispatch.once { $weak_observer_map = {} }
|
314
|
-
$weak_observer_map
|
315
|
-
end
|
316
|
-
def self.get(obj)
|
317
|
-
return obj if obj.is_a?(WeakObserverProxy)
|
318
|
-
weak_observer_map[obj.object_id] || new(obj)
|
319
|
-
end
|
320
|
-
end
|
321
|
-
|
322
|
-
class OnDeallocInternalObject
|
323
|
-
attr_accessor :description, :block
|
324
|
-
rmext_weak_attr_accessor :obj
|
325
|
-
def self.create(description, obj, block)
|
326
|
-
x = new
|
327
|
-
x.description = description
|
328
|
-
x.obj = obj
|
329
|
-
x.block = block
|
330
|
-
x
|
331
|
-
end
|
332
|
-
def dealloc
|
333
|
-
# p "dealloc OnDeallocInternalObject #{description}"
|
334
|
-
if block
|
335
|
-
block.call(obj)
|
336
|
-
self.block = nil
|
337
|
-
end
|
338
|
-
super
|
339
|
-
end
|
340
|
-
end
|
341
|
-
|
342
|
-
end
|