humancode.us

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

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 weakSelf everywhere, only once inside the block:

id strongSelf = weakSelf;

and then either assert that strongSelf != nil, or test it for nil. After that, use strongSelf everywhere. The problem with using weakSelf more 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 nil.

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 init and dealloc). Remember, referring to ivars can cause a strong reference to self. The example code should look like this:

weakSelf.localCounter++;

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 and stop. 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).

Use assert(), not NSAssert()

NSAssert throws an exception. This is not what you wantWhen 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.

Bonus:

assert()
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.