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)