This is a topic that I have considered writing about for some time now; in fact, I have a draft post from 2011, right after I finally decided to start using blocks. That post touched on a few areas of nuance in block memory management, but it basically all boiled down to: Memory Management and Blocks Can Be Tricky. I thought most of its talking points were tired, old news at this point — it seems I was wrong.
Last night I saw a flurry of tweets linking to a blog post about NSNotificationCenter’s block based API being ‘harmful’. I use this API fairly heavily, and was interested to see what kind of trouble I was in for. Drew Crawford does a great job of providing a narrative very similar to my first WTF moments from the early days of familiarizing myself with blocks. That said, I feel like the article’s title would be more accurate as: NSNotificationCenter with blocks considered harmful, unless you understand how self is captured.
Drew did a great job of putting together a simple test to illustrate that his notifications were firing more often than he’d expect (seriously, I love the test driven breakdown of the problem), and made the project available on a GitHub repo. Unfortunately, you have to wade through 700 words, all setting the stage for the takedown of the block based API, before it’s made clear that the culprit is actually a bug in the code that removes his block as a notification center observer. And this is where we veer a bit off course.
Blocks Hate Selfies
Capturing self is a fairly well known “sharp corner” of using blocks. Avoiding it is also generally well understood. I have this defined as a code snippet, and use it regularly.
__weak typeof(self) wSelf = self
I thought the dangers of directly accessing ivars in a block was also well understood; but possibly not. Here’s a reminder: ^{ _myIvar; }; is actually like writing ^{ self->_myIvar; };
. In this form, it’s easy to see why self
is captured/retained. Is _myIvar
an object? Guess what, the block doesn’t even retain it at all, just self
.
Harmful API
I didn’t set out to write this as a takedown piece, but as a correction of facts, and encouragement of philosophy. Drew plainly states that -[NSNotificationCenter addObserverForName:object:queue:usingBlock:]
is banned from his codebases, but to get there, had first had to cite a litany of tenuously linked data points. Sometimes documentation is wrong, file a bug report. Macros are sometimes tricky, blog about them. New syntax is sometimes confusing, learn about it.
Blocks can be hard; I suggest really learning about their memory implications, rather than dismiss API which uses them. Lots of API can be hard to use, LOTS of API does bad things when used incorrectly. Hell, even BOOL
has some gotchas.
Remember that the root of this discussion was the importance of deregistering an observer, which is still a problem without block based notifications! Admittedly, retain loops can be challenging to debug. You know where else I see a lot of accidental retain loops? Classes implementing a delegate. I’ve seen many (mostly new) cocoa developers accidentally cause retain loops when they retain their delegate, but so far I haven’t seen anyone calling for a banishment of the delegate pattern.
Don’t Run With Knives
Since they were introduced, I’ve been commenting on (and bitten by) the vast amount of rope that blocks provide a developer with. But try to understand the common patterns and pitfalls of the tools!
With blocks, always tread lightly with self. With blocks that have a lifetime scoped to their parent’s lifetime tread really fucking lightly with self. Stop thinking about your long lived blocks as snippets of code, and start thinking of them as 3rd party objects — apply memory management rules accordingly.
Tweet