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.