rm-extensions 0.0.9 → 0.1.0
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.
- checksums.yaml +4 -4
- data/README.md +13 -132
- data/lib/motion/accessors.rb +2 -4
- data/lib/motion/observation.rb +135 -51
- data/lib/motion/queues.rb +6 -36
- data/lib/rm-extensions/version.rb +1 -1
- data/lib/rm-extensions.rb +0 -2
- data/rm-extensions.gemspec +0 -1
- metadata +3 -19
- data/lib/motion/context.rb +0 -118
- data/lib/motion/retention.rb +0 -45
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9492c875dd1cedac32f756c747a6bb1dec24f2ac
|
4
|
+
data.tar.gz: 3f98058ac2b7e195413706d586332a135ad72bad
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: fafe79e35d321d4999b7ebbd2a5c11addc69a42f9b74d28ab087a9ba9cb59fed73c23ae38ec6b31152dee1b4dba62da70f71d342cd8fc7259616da004c4b2db9
|
7
|
+
data.tar.gz: 6a8071578eabf1f8c7404c920812693cf137b26ca4c02c1fe00f34f8d6f5750bb69b49a5aa54d4cabefaf66f4474bb750cace04cbaf54d89743a486f8999661a
|
data/README.md
CHANGED
@@ -3,11 +3,11 @@ RMExtensions
|
|
3
3
|
|
4
4
|
#### Extensions and helpers for dealing with various areas of rubymotion.
|
5
5
|
|
6
|
-
## Observation
|
6
|
+
## Observation/KVO, Events
|
7
7
|
|
8
|
-
#### Make observations without needing to clean up/unobserve
|
8
|
+
#### Make observations without needing to clean up/unobserve
|
9
9
|
|
10
|
-
Call from anywhere on anything
|
10
|
+
Call from anywhere on anything:
|
11
11
|
|
12
12
|
```ruby
|
13
13
|
class MyViewController < UIViewController
|
@@ -16,27 +16,26 @@ class MyViewController < UIViewController
|
|
16
16
|
rmext_observe(@model, "name") do |val|
|
17
17
|
p "name is #{val}"
|
18
18
|
end
|
19
|
+
foo.on(:some_event) do |val|
|
20
|
+
p "some_event called with #{val.inspect}"
|
21
|
+
end
|
19
22
|
end
|
20
23
|
end
|
24
|
+
def test_trigger
|
25
|
+
foo.trigger(:some_event, "hello!")
|
26
|
+
end
|
21
27
|
end
|
22
28
|
```
|
23
29
|
|
24
|
-
|
25
|
-
|
26
|
-
Differences:
|
30
|
+
Differences from BW::KVO and BW::Reactor::Eventable:
|
27
31
|
|
28
|
-
- No need to include
|
32
|
+
- No need to include a module in the class you wish to use it on
|
29
33
|
- The default is to observe and immediately fire the supplied callback
|
30
34
|
- The callback only takes one argument, the new value
|
31
35
|
- the object observing is not retained, and when it is deallocated, the observation
|
32
36
|
will be removed automatically for you. there is typically no need to clean up
|
33
37
|
and unobserve in viewWillDisappear, or similar.
|
34
|
-
-
|
35
|
-
object shouldnt incur any retain cycles.
|
36
|
-
|
37
|
-
Similarities:
|
38
|
-
|
39
|
-
- the object observed is retained
|
38
|
+
- the observation actually happens on a proxy object
|
40
39
|
|
41
40
|
|
42
41
|
## Accessors
|
@@ -79,13 +78,10 @@ end
|
|
79
78
|
|
80
79
|
# now you can verify the controller gets deallocated by calling #add_view_controller
|
81
80
|
# 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
81
|
```
|
86
82
|
## Queues
|
87
83
|
|
88
|
-
#### Wraps GCD
|
84
|
+
#### Wraps GCD:
|
89
85
|
|
90
86
|
```ruby
|
91
87
|
# note +i+ will appear in order, and the thread will never change (main)
|
@@ -110,121 +106,6 @@ end
|
|
110
106
|
end
|
111
107
|
```
|
112
108
|
|
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)
|
131
|
-
|
132
|
-
# when button is tapped, you will get this:
|
133
|
-
# >> Program received signal EXC_BAD_ACCESS, Could not access memory.
|
134
|
-
|
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
109
|
Installation
|
229
110
|
-----------------
|
230
111
|
|
data/lib/motion/accessors.rb
CHANGED
@@ -4,10 +4,8 @@ module RMExtensions
|
|
4
4
|
|
5
5
|
module Accessors
|
6
6
|
|
7
|
-
# creates an +attr_accessor+ like behavior, but the objects are
|
8
|
-
#
|
9
|
-
# when you want to have access to an object in a place that isnt responsible
|
10
|
-
# for that object's lifecycle.
|
7
|
+
# creates an +attr_accessor+ like behavior, but the objects are
|
8
|
+
# stored with a WeakRef.
|
11
9
|
# does not conform to KVO like attr_accessor does.
|
12
10
|
def rmext_weak_attr_accessor(*attrs)
|
13
11
|
attrs.each do |attr|
|
data/lib/motion/observation.rb
CHANGED
@@ -4,6 +4,10 @@ module RMExtensions
|
|
4
4
|
|
5
5
|
module Observation
|
6
6
|
|
7
|
+
def rmext_observation_proxy
|
8
|
+
@rmext_observation_proxy ||= ObservationProxy.new(self.inspect)
|
9
|
+
end
|
10
|
+
|
7
11
|
# observe an object.key. takes a block that will be called with the
|
8
12
|
# new value upon change.
|
9
13
|
#
|
@@ -11,85 +15,165 @@ module RMExtensions
|
|
11
15
|
# p "name is #{val}"
|
12
16
|
# end
|
13
17
|
def rmext_observe_passive(object, key, &block)
|
14
|
-
|
15
|
-
b = -> (old_value, new_value) do
|
16
|
-
block.call(new_value) unless block.nil?
|
17
|
-
end
|
18
|
-
wop.observe(object, key, &b)
|
18
|
+
rmext_observation_proxy.observe(object, key, &block)
|
19
19
|
end
|
20
20
|
|
21
21
|
# like +rmext_observe_passive+ but additionally fires the callback immediately.
|
22
22
|
def rmext_observe(object, key, &block)
|
23
|
-
# p "+ rmext_observe", self, object, key
|
24
23
|
rmext_observe_passive(object, key, &block)
|
25
24
|
block.call(object.send(key)) unless block.nil?
|
26
25
|
end
|
27
26
|
|
28
27
|
# unobserve an existing observation
|
29
28
|
def rmext_unobserve(object, key)
|
30
|
-
|
31
|
-
|
32
|
-
|
29
|
+
if @rmext_observation_proxy
|
30
|
+
@rmext_observation_proxy.unobserve(object, key)
|
31
|
+
end
|
33
32
|
end
|
34
33
|
|
35
34
|
# unobserve all existing observations
|
36
35
|
def rmext_unobserve_all
|
37
|
-
|
38
|
-
|
36
|
+
if @rmext_observation_proxy
|
37
|
+
@rmext_observation_proxy.unobserve_all
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# register a callback when an event is trigger on this object
|
42
|
+
def rmext_on(event, &block)
|
43
|
+
rmext_observation_proxy.on(event, &block)
|
44
|
+
end
|
45
|
+
|
46
|
+
# remove a specific callback for an event on this object
|
47
|
+
def rmext_off(event, &block)
|
48
|
+
if @rmext_observation_proxy
|
49
|
+
@rmext_observation_proxy.off(event, &block)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# remove all event callbacks on this object
|
54
|
+
def rmext_off_all
|
55
|
+
if @rmext_observation_proxy
|
56
|
+
@rmext_observation_proxy.off_all
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# trigger an event with args on this object
|
61
|
+
def rmext_trigger(event, *args)
|
62
|
+
if @rmext_observation_proxy
|
63
|
+
@rmext_observation_proxy.trigger(event, *args)
|
64
|
+
end
|
39
65
|
end
|
40
66
|
|
41
67
|
end
|
42
68
|
|
43
69
|
end
|
44
70
|
|
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
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
def initialize(
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
self.
|
57
|
-
strong_object.rmext_on_dealloc(&kill_observation_proc)
|
71
|
+
# # Proxy class used to hold the actual observation and watches for the real
|
72
|
+
# # class intended to hold the observation to be deallocated, so the
|
73
|
+
# # observation can be cleaned up.
|
74
|
+
class ObservationProxy
|
75
|
+
COLLECTION_OPERATIONS = [ NSKeyValueChangeInsertion, NSKeyValueChangeRemoval, NSKeyValueChangeReplacement ]
|
76
|
+
DEFAULT_OPTIONS = NSKeyValueObservingOptionNew
|
77
|
+
|
78
|
+
def initialize(desc)
|
79
|
+
@desc = desc
|
80
|
+
@events = {}
|
81
|
+
@targets = {}
|
82
|
+
# p "created #{self.inspect} for #{@desc}"
|
58
83
|
end
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
unobserve_all
|
67
|
-
self.class.weak_observer_map.delete(strong_object_id)
|
68
|
-
}
|
84
|
+
|
85
|
+
# clean up on dellocation
|
86
|
+
def dealloc
|
87
|
+
# p "dealloc #{self.inspect} for #{@desc}"
|
88
|
+
unobserve_all
|
89
|
+
off_all
|
90
|
+
super
|
69
91
|
end
|
70
|
-
|
71
|
-
def
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
92
|
+
|
93
|
+
def observe(target, key_path, &block)
|
94
|
+
target.addObserver(self, forKeyPath:key_path, options:DEFAULT_OPTIONS, context:nil) unless registered?(target, key_path)
|
95
|
+
add_observer_block(target, key_path, &block)
|
96
|
+
end
|
97
|
+
|
98
|
+
def unobserve(target, key_path)
|
99
|
+
return unless registered?(target, key_path)
|
100
|
+
target.removeObserver(self, forKeyPath:key_path)
|
101
|
+
remove_observer_block(target, key_path)
|
102
|
+
end
|
103
|
+
|
104
|
+
def remove_observer_block(target, key_path)
|
105
|
+
return if target.nil? || key_path.nil?
|
106
|
+
|
107
|
+
key_paths = @targets[target]
|
108
|
+
if !key_paths.nil? && key_paths.has_key?(key_path.to_s)
|
109
|
+
key_paths.delete(key_path.to_s)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
def unobserve_all
|
114
|
+
keys = @targets.keys.clone
|
115
|
+
while keys.size > 0
|
116
|
+
target = keys.pop
|
117
|
+
target_hash = @targets[target]
|
118
|
+
paths = target_hash.keys.clone
|
119
|
+
while paths.size > 0
|
120
|
+
key_path = paths.pop
|
121
|
+
target.removeObserver(self, forKeyPath:key_path)
|
76
122
|
end
|
77
123
|
end
|
78
|
-
|
124
|
+
@targets.clear
|
125
|
+
end
|
126
|
+
|
127
|
+
def registered?(target, key_path)
|
128
|
+
!target.nil? && !@targets[target].nil? && @targets[target].has_key?(key_path.to_s)
|
129
|
+
end
|
130
|
+
|
131
|
+
def add_observer_block(target, key_path, &block)
|
132
|
+
return if target.nil? || key_path.nil? || block.nil?
|
133
|
+
@targets[target] ||= {}
|
134
|
+
@targets[target][key_path.to_s] ||= []
|
135
|
+
@targets[target][key_path.to_s] << block
|
136
|
+
end
|
137
|
+
|
138
|
+
# NSKeyValueObserving Protocol
|
139
|
+
|
140
|
+
def observeValueForKeyPath(key_path, ofObject:target, change:change, context:context)
|
141
|
+
return if target.nil?
|
142
|
+
key_paths = @targets[target] || {}
|
143
|
+
blocks = key_paths[key_path] || []
|
144
|
+
blocks.each do |block|
|
145
|
+
args = [ change[NSKeyValueChangeNewKey] ]
|
146
|
+
args << change[NSKeyValueChangeIndexesKey] if collection?(change)
|
147
|
+
block.call(*args)
|
148
|
+
end
|
79
149
|
end
|
80
|
-
|
81
|
-
|
150
|
+
|
151
|
+
def collection?(change)
|
152
|
+
COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
|
82
153
|
end
|
83
|
-
|
84
|
-
|
154
|
+
|
155
|
+
def on(event, &block)
|
156
|
+
return if event.nil? || block.nil?
|
157
|
+
@events[event.to_s] ||= []
|
158
|
+
@events[event.to_s] << block
|
85
159
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
160
|
+
|
161
|
+
def off(event, &block)
|
162
|
+
return if event.nil? || block.nil? || !@events.key?(event.to_s)
|
163
|
+
@events[event.to_s].delete_if { |b| b == block }
|
164
|
+
nil
|
89
165
|
end
|
90
|
-
|
91
|
-
|
92
|
-
|
166
|
+
|
167
|
+
def off_all
|
168
|
+
@events.clear
|
169
|
+
end
|
170
|
+
|
171
|
+
def trigger(event, *args)
|
172
|
+
return if event.nil? || !@events.key?(event.to_s)
|
173
|
+
@events[event.to_s].each do |block|
|
174
|
+
block.call(*args)
|
175
|
+
end
|
176
|
+
nil
|
93
177
|
end
|
94
178
|
end
|
95
179
|
|
data/lib/motion/queues.rb
CHANGED
@@ -9,55 +9,25 @@ module RMExtensions
|
|
9
9
|
|
10
10
|
module ObjectExtensions
|
11
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
12
|
# These helper methods are all for async mode.
|
25
13
|
module Queues
|
26
14
|
|
27
15
|
# execute a block on the main queue, asynchronously.
|
28
16
|
def rmext_on_main_q(&block)
|
29
|
-
|
30
|
-
x.block = -> do
|
31
|
-
block.call
|
32
|
-
x.detach!
|
33
|
-
end
|
34
|
-
Dispatch::Queue.main.async(&x.block)
|
35
|
-
end
|
17
|
+
Dispatch::Queue.main.async(&block)
|
36
18
|
end
|
37
19
|
|
38
20
|
# execute a block on a serial queue, asynchronously.
|
39
21
|
def rmext_on_serial_q(q, &block)
|
40
|
-
|
41
|
-
|
42
|
-
|
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
|
22
|
+
key = "#{NSBundle.mainBundle.bundleIdentifier}.serial.#{q}"
|
23
|
+
::RMExtensions.serial_qs[key] ||= Dispatch::Queue.new(key)
|
24
|
+
::RMExtensions.serial_qs[key].async(&block)
|
49
25
|
end
|
50
26
|
|
51
27
|
# execute a block on a concurrent queue, asynchronously.
|
52
28
|
def rmext_on_concurrent_q(q, &block)
|
53
|
-
|
54
|
-
|
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
|
29
|
+
key = "#{NSBundle.mainBundle.bundleIdentifier}.concurrent.#{q}"
|
30
|
+
Dispatch::Queue.concurrent(key).async(&block)
|
61
31
|
end
|
62
32
|
|
63
33
|
end
|
data/lib/rm-extensions.rb
CHANGED
data/rm-extensions.gemspec
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
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.1.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: 2013-
|
12
|
-
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bubble-wrap
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - '>='
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: 1.1.5
|
20
|
-
type: :runtime
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - '>='
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: 1.1.5
|
11
|
+
date: 2013-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
27
13
|
description: Extensions and helpers for dealing with various areas of rubymotion
|
28
14
|
email:
|
29
15
|
- joenoon@gmail.com
|
@@ -37,11 +23,9 @@ files:
|
|
37
23
|
- README.md
|
38
24
|
- Rakefile
|
39
25
|
- lib/motion/accessors.rb
|
40
|
-
- lib/motion/context.rb
|
41
26
|
- lib/motion/deallocation.rb
|
42
27
|
- lib/motion/observation.rb
|
43
28
|
- lib/motion/queues.rb
|
44
|
-
- lib/motion/retention.rb
|
45
29
|
- lib/motion/util.rb
|
46
30
|
- lib/rm-extensions.rb
|
47
31
|
- lib/rm-extensions/version.rb
|
data/lib/motion/context.rb
DELETED
@@ -1,118 +0,0 @@
|
|
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)
|
data/lib/motion/retention.rb
DELETED
@@ -1,45 +0,0 @@
|
|
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
|
-
alias_method :rmext_release!, :rmext_detach!
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
end
|
43
|
-
|
44
|
-
end
|
45
|
-
Object.send(:include, ::RMExtensions::ObjectExtensions::Retention)
|