humancode.us

Block APIs are NOT harmful

July 17, 2014

Note: This is a repost of an older post that was published on my tumblr blog. Before I begin writing more posts on the subject of programming, I thought I’d practice by reposting something I’ve already written. This post has been re-edited for clarity.

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 similar 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 a block which is not released when the object is no longer needed. 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

In my opinion, the issues described in the blog post don’t show that block-based APIs are “harmful”. Rather, they show that we need to pay extra attention when we use blocks as event handlers. There are subtle problems that must be avoided by adopting certain 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, and thus create a retain cycle. Even referring to any property or ivar of self will implicitly create a strong reference to self.

For event handler blocks (that is, blocks that are retained for a long time, and potentially invoked multiple times, by another object), use weak references to refer to self, and transform them into strong references once inside the block.

__weak MyObject* weakSelf = self;
[self.service setEventHandler: ^{
    MyObject *strongSelf = weakSelf;
    if (strongSelf) {
        [strongSelf doSomething];
    }
}];

(Note that you don’t need to do this for completion blocks (that is, blocks that will be released once they have been invoked at the end of a one-shot activity). In fact, you should use strong references to self, because you want to keep your object around until the event completes.)

Remember to shut down event-handling before releasing your final strong reference to your object. See the Decouple resource management section below for more information.

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. Referring to properties uses the same kind of reference that you have for the object that owns the property; if that object reference is weak, it will remain a weak reference.

In other words:

// Called within a member function
__weak MyObject *weakSelf = self;
[object setBlock: ^{
    weakSelf.localCounter++;
}];

will not create a strong reference to self, but:

// Called within a member function
[object setBlock: ^{
    _localCounter++; // _localCounter is an ivar
}];

will.

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; so you can’t count on dealloc being called when you drop your last reference to an object.

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 a method, and call it when appropriate.

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 (dealloc should also call stop if necessary).

Use assert(), not NSAssert()

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.

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.