Borkware Miniblog

September 6, 2010

Block Retain Cycles

Filed under: amosxp, programming — Mark Dalrymple @ 10:37 pm

So I’ve seen in a couple of places where you can get retain cycles with Objective-C blocks, and then have to do some contortions to get a __block-storage-class self pointer that won’t be auto-retained.

But I couldn’t find a simple example to demonstrate the problem, and I want to verify the problem before it gets cast into dead trees or implanted into student’s minds.

So here is minimal example. First is a typedef for a block pointer, and a simple object that holds on to the block:

// Just a simple block pointer that asks for nothing and gives nothing.
typedef void (^BlockHead)(void);

// The leaky object.
@interface Leakzor : NSObject {
    NSString *string;
    BlockHead blockhead;
}

// Print |string|.
- (void) funk;

@end // Leakzor

This will leak the object and the block due to the retain cycle:

- (id) init {
    if ((self = [super init])) {
        string = @"snork";

        // |string| is the same as self->string, so |self| is retained.
        blockhead = Block_copy(^{
                NSLog (@"string is %@", string);
            });
    }
    return self;
} // init

If you compile blockcycle with -DRETAIN_CYCLE=1 you won’t see the dealloc NSLog. Why?

blockhead has caused self to be retained. self won’t be released until blockhead is cleaned up in -dealloc. But -dealloc won’t get called because self is still retained by the block. This is a classic retain cycle.

So how to fix it? With these hoops:

        // |blockSelf| is __block scope, so won't be auto-retained
        __block Leakzor *blockSelf = self;
        blockhead = Block_copy(^{
                NSLog (@"string is %@", blockSelf->string);
            });

So now I access string by using the self pointer, but in the shape of a __block-storage-class local variable. This doesn’t have the retain behavior that ordinary variable capture has.

Does this mean that I’m going to be peppering every block that refers to self, directly or indirectly, with this stuff? Nope. But it’s something to keep in mind, especially if you’re making a copy of a block and then dealing with its cleanup in -dealloc (vs some kind of good-bye kiss method).

3 Comments »

  1. Teeeechincally, __block is a storage class and doesn’t actually affect scoping rules.

    Anyway, great post. The one that trips me up more often is that using ivars causes an implicit retain of self. So frustrating — can’t wait until we can all switch over to GC.

    Comment by Colin Barrett — September 11, 2010 @ 1:49 pm

  2. As usual, you are correct, sir. Corrected my former use of “block-scoped” to “block-storage-class”.

    Comment by Mark Dalrymple — September 11, 2010 @ 1:55 pm

  3. In a couple of spots where I declare a great many blocks, I use a trick to help me avoid accidentally referencing self

    {
    __block __typeof__(self) _self = self;
    const void *self = NULL; // shadow the real self
    #pragma unused(self)
    // do all your block stuff here
    }

    This works reasonably well. If you ever reference self, it will give you an odd error which isn’t really understandable but does point to the right line. The only problem is, at least with GCC (haven’t tested with Clang), references to ivars still implicitly reference the real self rather than the shadowed self. This means you still have to be careful about accessing ivars, but of course that can be solved by adopting a policy of using property access exclusively outside of init/dealloc/accessors.

    Comment by Kevin Ballard — September 22, 2010 @ 7:01 pm


RSS feed for comments on this post.

Leave a reply to Kevin Ballard Cancel reply

Create a free website or blog at WordPress.com.