Block APIs are NOT harmful
November 20, 2013
I take exception to Drew Crawford’s recent post that block-based APIs introduce “harmful” surprising side effects into Objective-C, especially under ARC. Drew’s post calls attention to
NSNotificationCenter in particular, but really, any block-based API is susceptible to these same kinds of pitfalls.
I would encourage you to download a copy of Drew’s example code as you read his blog post to understand the problem he’s describing.
The problem in the sample code has to do with an object which registers itself as an observer of a notification with a block as the event handler. The block modifies a global variable and a local variable, and performs an assertion. Running the code within the Unit Test and instantiating multiple objects in a loop yields the somewhat surprising result that the notification handler is never removed. In fact, the objects are never deallocated at all.
The crux of the problem? The fact that
self is strongly captured by the block. Drew’s post walks through a series of unsuccessful attempts at extricating one’s code from this snare, revealing more subtleties along the way.
Not harmful, but do be careful
I cannot conclude that the issues described in the blog post are “harmful”. Instead, we should view them as the cognitive price we have to pay for the convenience that blocks provide. These pitfalls can be avoided by adopting better coding habits.
Here are some rules of thumb when dealing with blocks.
Refer to yourself weakly
As Drew’s blog post makes clear, among the first things that a block-programming newbie learns is how easy it is to strongly capture
self in a block. Even referring to any property or ivar of
self will implicitly create a strong reference to
self. So, unless you actually want to strongly retain yourself (you do want to do this when passing
self to a completion block, for example), you’ll want to type this before describing a block:
__weak MyClass *weakSelf = self;
You should then use
weakSelf everywhere in the block.
Update: Thanks to noupvotesplease for pointing this out:
you shouldn’t use the
weakSelfeverywhere, only once inside the block:
id strongSelf = weakSelf;
and then either assert that
strongSelf != nil, or test it for
nil. After that, use
strongSelfeverywhere. The problem with using
weakSelfmore than once is that it can go nil at any time. I find it easier to do it this way than making sure every bit of code is safe for messaging
My response: Great advice! But you can get away without this if you follow these rules:
For completion blocks, use strong references to
self, because you want to keep your object around until the event completes, at which point the block will be released.
For event handlers, use weak references, but remember to shut down event-handling before releasing your final strong reference to the object (see the Decouple resource managementsection below). That way, there is always a strong reference somewhere that keeps the object around as long as it’s handling events.
Refer to properties, not ivars
This is good hygiene in modern Objective-C in any case, but it’s even more important in blocks: you should refer to properties rather than ivars everywhere (except in
dealloc). Remember, referring to ivars can cause a strong reference to
self. The example code should look like this:
Decouple resource management from object behavior
In one of the examples,
dealloc is used to remove the notification observer. This is a classic ARC mistake, because you don’t know when an object will actually be
dealloc’d. Put another way, under ARC, a programmer can deduce the minimum lifespan of an object, but never the maximum, because someone else could hold a strong reference to the object beyond your expected lifetime.
You should not put anything in
dealloc that does anything meaningful to the users of your object.
dealloc should be used to free resources, and nothing else. If you need to make your object stop doing something, express that as an explicit action.
The Attempt classes in the sample code could have a pair of methods called
start can be called after the object is initialized, and
stop should be called whenever the object should stop responding to events. (Of course,
dealloc should also call
stop if necessary).
NSAssert throws an exception. This is not what you want. When an invariant is violated, you want your program to die an instant death, not traipse through some library, throwing exceptions that can be caught and masked.
assert() takes a gun and shoots your program in its head. Blam. Debug time.
does not retain objects.
Not so harmful after all
Blocks aren’t harmful, they just need you to change some old habits. If you keep these rules of thumb in mind, the payoff you get from using blocks will outweigh the cost of entry.