ARC, Blocks, and retain cycle reminders
I’ve been neck-deep in Cocoa / iOS / mobile work lately, trying to inhale everything as quickly and thoroughly as possible. Each discovery of a new way of doing things is exciting - in exploring a new ecosystem, it’s been a blast being exposed to different design patterns, best practices, and libraries.
A fellow YCer introduced me recently to BlocksKit (and A2DynamicDelegate), two libraries that rejigger the standard delegate pattern of lots of the normal framework classes into a block. For example, consider the difference between the framework-standard code to show a UIActionSheet
:
And the same logic, with BlocksKit:
This feels sturdier, more compact, and more straightforward - especially considering my Ruby background. In integrating BlocksKit into my project, though, I started running into whispers about retain cycles - specifically, a situation in which a class has a strong reference to a block, which in turn has strong references to the local variables (often members of the class!) it might use or call.
After reading a bunch of different blog posts and StackOverflow answers discussing the same issue, I wanted to consolidate / quote a few of the most useful sources. In short, whenever using blocks (especially in ARC-enabled code, which handles most of the memory management for you), developers need to watch out for retain cycles and break them if necessary.
Blocks in Objective-C have one more very important difference from blocks in C in handling variables that reference objects. All local objects are automatically retained as they are referenced! If you reference an instance variable from a block declared in a method, this retains self, as you’re implicitly doing
self->theIvar
.
source: Programming with C Blocks. Click through for code snippets, examples, and a deeper discussion of Memory Management in blocks (section 4.2).
The
__block
qualifier allows a block to modify a captured variable:id x; __block id y; void (^block)(void) = ^{ x = [NSString string]; // error y = [NSString string]; // works };
Without ARC,
__block
also has the side effect of not retaining its contents when it’s captured by a block. Blocks will automatically retain and release any object pointers they capture, but__block
pointers are special-cased and act as a weak pointer. It’s become a common pattern to rely on this behavior by using__block
to avoid retain cycles.Under ARC,
__block
now retains its contents just like other captured object pointers. Code that uses__block
to avoid retain cycles won’t work anymore. Instead, use__weak
as described above.
source: Mike Ash on ARC - scroll down to the section on blocks.
In retrospect, I read a good number of these posts when I was first exploring iOS and ARC - I suppose it’s a testament to how much content is in some of these articles (or the complexity of changing ecosystems?) that I didn’t make all of the connections / pick up all of the tips that I should have.
You can use lifetime qualifiers to avoid strong reference cycles. For example, typically if you have a graph of objects arranged in a parent-child hierarchy and parents need to refer to their children and vice versa, then you make the parent-to-child relationship strong and the child-to-parent relationship weak. Other situations may be more subtle, particularly when they involve block objects.
In manual reference counting mode,
__block id x;
has the effect of not retainingx
. In ARC mode,__block id x;
defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use__unsafe_unretained __block id x;
. As the name__unsafe_unretained
implies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use__weak
(if you don’t need to support iOS 4 or OS X v10.6), or set the__block
value tonil
to break the retain cycle.
source: Use Lifetime Qualifiers to Avoid Strong Reference Cycles - click through for code examples. Unsurprisingly, Apple’s own documentation is the most thorough - if only I’d found it first!
Specifically, I wanted to know how best to handle the case in which I called -dismissViewControllerAnimated:completion:
when the user, say, clicked the “Done” button on the toolbar:
Thanks to the Apple docs, we have instead:
Time for a code audit - I hastily (aka giddily, aka excitedly) integrated BlocksKit yesterday without researching this too closely, but I know that I use blocks often elsewhere in my code, and it’s time to make sure everything’s correct before I push my latest set of commits.
Let me know what you think on Twitter.