Working around RubyMotion’s Memory Management Limitations

During my time using “RubyMotion”:http://www.rubymotion.com and “ReactiveCocoa”:http://github.com/ReactiveCocoa/ReactiveCocoa, I ran into a couple of pitfalls related to memory management. It should noted that these are _not_ bugs in RubyMotion or ReactiveCocoa but are instead just the reality of working in a runtime that is built on “Automatic Reference Counting”:http://en.wikipedia.org/wiki/Automatic_Reference_Counting.

h2. Lambdas and Blocks in RubyMotion

In RubyMotion, blocks and lambdas (objects that wrap the block) create a strong reference to self. Regardless of whether self is referenced explicitly or not — the reference is implicit in every block.

For example:

  class ViewController < UIViewController
    def viewDidLoad
      @block = lambda {}
    end
  end

At first glance, the code looks innocuous enough, but the reality is that an instance of a ViewController will never be deallocated. Since a lambda implicitly creates a reference to self and the lambda is assigned to an instance variable, it will create a cyclical reference.

h2. Self-Referential Observations in ReactiveCocoa

In ReactiveCocoa it is not uncommon to observe changes to properties on self. However, there are strict memory management semantics surrounding this kind of pattern since it creates a cyclical reference to self.

For example:

       
  __weak id weakSelf = self
  [RACObserve(self, firstName) subscribeNext:^(NSString *firstName) {
    id strongSelf = weakSelf;
     // .. do stuff
  }];

The above code snippet takes special steps to ensure that a weak reference of self is used. Where a local strong reference of self is used in the context of the block only. This prevents a cyclical reference since _one_ of the references is _weak_.

The above code can be simplified using "EXTScope":https://github.com/jspahrsummers/libextobjc:

       
  @weakify(self)
  [RACObserve(self, firstName) subscribeNext:^(NSString *firstName) {
     @strongify(self)
     // .. do stuff
  }];

h2. A Workaround for ReactiveCocoa & RubyMotion Used Together

Unfortunately, there are no affordances for working around cyclical references involving observations on self in RubyMotion. Which means that extra care has to be taken when using ReactiveCocoa and RubyMotion together.

The following code snippet will create a cyclical reference:

 
  rac.first_name.subscribeNext -> (first_name) do
    // Do stuff
  end

Unfortunately, we cannot do anything like this:

 
  weak_self = WeakRef.new(self)
  rac.first_name.subscribeNext -> (first_name) do
    strong_self = weak_self
    // Do stuff
  end

Because lambdas _always_ create an implicit strong reference to self, there is no away to properly manage the object graph. In order to work around this problem, it requires that a _proxy_ object is used where it has a _weak_ reference to the object it is observing.

For example:

  class ViewModel
    attr_reader :controller
    def initialize(controller)
      @controller = WeakRef.new(controller)
      rac.controller.firstName.subscribeNext -> (x) do
        // do stuff
      end
    end
  end
  
  class ViewController
    def viewDidLoad
      @model = ViewModel.new(self)
    end
  end

This is an effective workaround for self-referential issues in RubyMotion. Moreover, it also moves logic into an object that will easier to test going forward.

h2. Conclusion

RubyMotion, Objective-C, and ReactiveCocoa tend to work effectively together. However, the memory semantics that are available in Objective-C are not available in RubyMotion. Which means that extra care needs to be taken when dealing with lambdas and cyclical relationships, especially since it is exceedingly hard to test for as well.
 

_*Update*_

The RubyMotion team has addressed the lambda memory management limitation. It will be apart of the next major release of RubyMotion.

It will now be possible to do the following:

 
  rac.first_name.subscribeNext -> (first_name) do
    // Do stuff
  end.weak!

Where #weak! returns a lambda where self is referenced weakly.