rm-extensions 0.0.9 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: d76c35600cd3f43ee72e64f70d19bc234a6534c4
4
- data.tar.gz: a71afeb1e4b2d9dfdf87c22559f788aaca4ff9b8
3
+ metadata.gz: 9492c875dd1cedac32f756c747a6bb1dec24f2ac
4
+ data.tar.gz: 3f98058ac2b7e195413706d586332a135ad72bad
5
5
  SHA512:
6
- metadata.gz: 18d10f94655198437b6ae2a83f19bcbd90d4ec84cc628a0830b32ebd9b96c4464292019d71daf005ee0cd90210906e23dbb2059933cc49627d28ecc8dcd81ae0
7
- data.tar.gz: 6e721dae784ef44f950d2730ddceadaf20b18ed512d82029867289c564e8b32042366aa3e52975a48ac3a4e22558c98701e2719581e30fe9e505cd5e5bde5203
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, and avoid retain-cycles
8
+ #### Make observations without needing to clean up/unobserve
9
9
 
10
- Call from anywhere on anything without prior inclusion of BW::KVO:
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
- Under the hood this piggy-backs on Bubblewrap's KVO implementation.
25
-
26
- Differences:
30
+ Differences from BW::KVO and BW::Reactor::Eventable:
27
31
 
28
- - No need to include BW::KVO anywhere
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
- - 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
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 to avoid complier issues with blocks and also ensures the block passed is retained until executed on the queue:
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
 
@@ -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 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.
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|
@@ -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
- 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)
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
- wop = ::RMExtensions::WeakObserverProxy.get(self)
31
- wop.unobserve(object, key)
32
- wop.clear_empty_targets!
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
- wop = ::RMExtensions::WeakObserverProxy.get(self)
38
- wop.unobserve_all
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 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)
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
- # 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
- }
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
- # 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)
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
- nil
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
- def inspect
81
- "#{strong_class_name}:#{strong_object_id}"
150
+
151
+ def collection?(change)
152
+ COLLECTION_OPERATIONS.include?(change[NSKeyValueChangeKindKey])
82
153
  end
83
- def targets
84
- @targets
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
- def self.weak_observer_map
87
- Dispatch.once { @weak_observer_map = {} }
88
- @weak_observer_map
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
- def self.get(obj)
91
- return obj if obj.is_a?(WeakObserverProxy)
92
- weak_observer_map[obj.object_id] || new(obj)
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
- 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
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
- 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
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
- 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
29
+ key = "#{NSBundle.mainBundle.bundleIdentifier}.concurrent.#{q}"
30
+ Dispatch::Queue.concurrent(key).async(&block)
61
31
  end
62
32
 
63
33
  end
@@ -1,3 +1,3 @@
1
1
  module RMExtensions
2
- VERSION = "0.0.9"
2
+ VERSION = "0.1.0"
3
3
  end
data/lib/rm-extensions.rb CHANGED
@@ -7,10 +7,8 @@ end
7
7
  Motion::Project::App.setup do |app|
8
8
  %w(
9
9
  util
10
- retention
11
10
  accessors
12
11
  deallocation
13
- context
14
12
  observation
15
13
  queues
16
14
  ).reverse.each do |x|
@@ -16,5 +16,4 @@ 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')
20
19
  end
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.9
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-06-13 00:00:00.000000000 Z
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
@@ -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)
@@ -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)