README.md in rm-extensions-0.0.5 vs README.md in rm-extensions-0.0.6
- old
+ new
@@ -1,41 +1,262 @@
-# RMExtensions
+RMExtensions
+-----------------
-Extensions and helpers for dealing with various areas of rubymotion:
+#### Extensions and helpers for dealing with various areas of rubymotion.
-- memory management
-- block/scope/local variable issues
-- GCD blocks
-- retaining objects through async procedures
-- weak attr_accessors
+## Observation
-Currently depends on bubblewrap.
+#### Make observations without needing to clean up/unobserve, and avoid retain-cycles
-AssociatedObject objc runtime taken from BlocksKit, modified to work with rubymotion.
+Call from anywhere on anything without prior inclusion of BW::KVO:
-## Installation
+```ruby
+class MyViewController < UIViewController
+ def viewDidLoad
+ super.tap do
+ rmext_observe(@model, "name") do |val|
+ p "name is #{val}"
+ end
+ end
+ end
+end
+```
-Add this line to your application's Gemfile:
+Under the hood this piggy-backs on Bubblewrap's KVO implementation.
- gem 'rm-extensions'
+Differences:
-Add this line to your application's Rakefile:
+- No need to include BW::KVO anywhere
+- The default is to observe and immediately fire the supplied callback
+- The callback only takes one argument, the new value
+- the object observing is not retained, and when it is deallocated, the observation
+ will be removed automatically for you. there is typically no need to clean up
+ and unobserve in viewWillDisappear, or similar.
+- because the observation actually happens on an unretained proxy object, the real
+ object shouldnt incur any retain cycles.
- pod 'BlocksKit'
+Similarities:
+- the object observed is retained
+
+
+## Accessors
+
+#### weak attr_accessors when you need to avoid retain-cycles:
+
+```ruby
+
+class MyView < UIView
+ rmext_weak_attr_accessor :delegate
+end
+
+class MyViewController < UIViewController
+ def viewDidLoad
+ super.tap do
+ v = MyView.alloc.initWithFrame(CGRectZero)
+ view.addSubview(v)
+ # if delegate was a normal attr_accessor, this controller could never be deallocated
+ v.delegate = self
+ end
+ end
+end
+
+```
+
+## Deallocation
+
+#### watch for an object to deallocate, and execute a callback:
+
+```ruby
+def add_view_controller
+ controller = UIViewController.alloc.init
+ controller.rmext_on_dealloc(&test_dealloc_proc)
+ navigationController.pushViewController(controller, animated: true)
+end
+
+def test_dealloc_proc
+ proc { |x| p "it deallocated!" }
+end
+
+# now you can verify the controller gets deallocated by calling #add_view_controller
+# and then popping it off the navigationController
+
+# you should be careful not to create the block inline, since it could easily create a retain cycle
+# depending what other objects are in scope.
+```
+## Queues
+
+#### Wraps GCD to avoid complier issues with blocks and also ensures the block passed is retained until executed on the queue:
+
+```ruby
+# note +i+ will appear in order, and the thread will never change (main)
+100.times do |i|
+ rmext_on_main_q do
+ p "i: #{i} thread: #{NSThread.currentThread}"
+ end
+end
+
+# note +i+ will appear in order, and the thread will change
+100.times do |i|
+ rmext_on_serial_q("testing") do
+ p "i: #{i} thread: #{NSThread.currentThread}"
+ end
+end
+
+# note +i+ will sometimes appear out of order, and the thread will change
+100.times do |i|
+ rmext_on_concurrent_q("testing") do
+ p "i: #{i} thread: #{NSThread.currentThread}"
+ end
+end
+```
+
+## Context
+
+#### break through local variable scope bugs, where using instance variables would mean your method is not re-entrant. retain objects through asynchronous operations.
+
+##### rmext_context
+
+```ruby
+# yields an object you can treat like an openstruct. you can get/set any property
+# on it. useful for scope issues where local variables wont work, and where instance
+# variables would clutter the object and not be re-entrant.
+
+# Consider this example:
+
+button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
+button.when_tapped do
+ button.setTitle("Tapped", forState:UIControlStateNormal)
+end
+view.addSubview(button)
+
+# when button is tapped, you will get this:
+# >> Program received signal EXC_BAD_ACCESS, Could not access memory.
+
+# Workaround using +rmext_context+:
+
+rmext_context do |x|
+ x.button = UIButton.buttonWithType(UIButtonTypeRoundedRect)
+ x.button.when_tapped do
+ x.button.setTitle("Tapped", forState:UIControlStateNormal)
+ end
+ view.addSubview(x.button)
+end
+
+# when button is tapped, it works.
+
+# a note about the different use cases for +rmext_context+ and +rmext_retained_context+,
+# because its important to understand when to use which, and what different purposes they
+# are for:
+
+# +rmext_context+ is used here instead of +rmext_retained_context+ because:
+
+# 1. the button is already going to be retained by the view its added to, so
+# there is no need for us to retain it explicitly.
+# 2. there would be no clear way to eventually "detach" it, since the button
+# could be clicked any number of times.
+```
+
+##### rmext_retained_context
+
+```ruby
+# like +rmext_context+ but the context is retained (as well as anything set on it) until you
+# explicitly call +detach!+ or +detach_on_death_of+ and that object is deallocated. prevents
+# deallocation of objects until you are done with them, for example through asynchronous
+# operations.
+
+# also has a useful shortcut for beginBackgroundTaskWithExpirationHandler/endBackgroundTask
+# via +begin_background!+. when you call +detach!+ the background task will be ended for you
+# as well.
+
+# use this over +rmext_context+ when you have a scenario when eventually you know everything
+# is complete, and can call +detach!+. for example, an operation that makes an http request,
+# uses the result to call another operation on a specific queue, and is finally considered
+# "finished" at some point in time in the future. there is a definitive "end", at some point
+# in the future.
+
+# example:
+
+rmext_retained_context do |x|
+ rmext_on_serial_q("my_serial_q") do
+ some_async_http_request do |results1|
+ x.results1 = results1
+ rmext_on_serial_q("my_serial_q") do
+ some_other_async_http_request do |results2|
+ x.results2 = results2
+ rmext_on_main_q do
+ p "results1", x.results1
+ p "results2", x.results2
+ x.detach!
+ end
+ end
+ end
+ end
+ end
+end
+```
+
+## Retention
+
+#### A type of retain/release that just uses rubymotion's memory-management rules instead of calling the native retain/release:
+
+```ruby
+class MyViewController < UITableViewController
+
+ # note here, if the view controller is deallocated during the http request (someone hits Back, etc),
+ # and then the http request finishes, and you try to call tableView.reloadData, it will be a
+ # EXC_BAD_ACCESS:
+ def fetch_unsafe
+ remote_http_request do |result|
+ @models = []
+ tableView.reloadData
+ end
+ end
+
+ # ensure self stay around long enough for the block to be called
+ def fetch
+ rmext_retain!
+ remote_http_request do |result|
+ @models = []
+ tableView.reloadData
+ rmext_detach!
+ end
+ end
+
+end
+```
+
+Installation
+-----------------
+
+Add this line to your application's Gemfile:
+
+ gem 'rm-extensions'
+
And then execute:
$ bundle
-## Usage
+* Currently depends on bubblewrap (for BW::KVO).
+* AssociatedObject objc runtime taken from BlocksKit, modified to work with rubymotion.
-Some code is commented. TODO.
+Contributing
+-----------------
-## Contributing
-
If you have a better way to accomplish anything this library is doing, please share!
1. Fork it
2. Create your feature branch (`git checkout -b my-new-feature`)
3. Commit your changes (`git commit -am 'Add some feature'`)
4. Push to the branch (`git push origin my-new-feature`)
5. Create new Pull Request
+
+License
+-----------------
+
+Please see [LICENSE](https://github.com/joenoon/rm-extensions/blob/master/LICENSE.txt) for licensing details.
+
+
+Author
+-----------------
+
+Joe Noon, [joenoon](https://github.com/joenoon)