Strong reference loop/memory leak in block

My first instinct to resolve the warning was to do this:

    ...
    UIImageView *thumbnailView = cell.thumbnailView; // <-- new local variable
    cell.actionBlock = ^{
        if ([UIDevice currentDevice].userInterfaceIdiom != UIUserInterfaceIdiomPad) return;
        NSString *itemKey = item.itemKey;
        UIImage *img = [[BNRImageStore sharedStore] imageForKey:itemKey];
        if (!img) return;
        CGRect rect = [self.view convertRect:thumbnailView.bounds fromView:thumbnailView]; // <-- used here instead of cell.thumbnailView
        ...

Am I just masking the problem, or does this resolve the issue? I tried to use the Profile / Leaks to see whether there is a leak, but even with the original code from the book, the Profiler didn’t seem to find a problem…?

That would solve the problem. Nice intuition! Just so you understand why:

This same retain cycle within blocks problem is taught in BNR’s Objective-C Programming chapter “Blocks,” but I struggled with understanding the concept until my co-worker explained it to me.

Here’s the issue. The BNRItemCell class has a strong reference to the actionBlock. It owns the block. So as long as the cell lives in memory, the block too, will live in memory. Recall that a block is able to capture the variables that it references. What does that mean? If you have a block like this:

void (^block)(void) = ^{

}

then nothing is captured (or enclosed is the term used in the book) since the block isn’t referring to anything.

What about the block in this method?

- (void)someMethod
{
    NSInteger x = 1;

    void (^capturingBlock)(void) = ^{
        NSLog(@"%ld", x);
    };
}

The block refers to the variable x, and so when the block is created, it captures the value of x, like a freeze frame, to use that value when the block is executed. But NSInteger is a primitive data type so its value is simply copied to another local primitive type within the block.

Let’s look at a block referring to an object:

- (void)someOtherMethod
{
    NSDate *now = [NSDate date];
    void (^capturingBlock)(void) = ^{
        NSLog(@"%@", now);
    };
}

When the block runs and encloses over an object, it captures the memory address of now, and owns (reference count + 1) whatever object is at that memory address. Again, this means that as long as the block is in memory, the now object will also exist in memory because the block is keeping it alive.

Do you see the problem the book was talking about? The problem with referring to cell within the actionBlock is that cell points strongly to its actionBlock property. The actionBlock itself references cell, and since it references cell, it encloses cell and holds a strong pointer to it. Right there is the retain cycle. The book advises using a weak pointer to the cell instead:

__weak BNRItemCell *weakCell = self;

When the block uses weakCell, the block will capture a reference back to cell, but this time, it’s now a weak reference and breaks the retain cycle.

Your solution works because instead of accessing the thumbnailView through the cell, you work around the cell by just pointing directly to the thumbnailView’s memory address. Since the thumbnail view doesn’t hold a reference back to the cell, you’re golden.

The book goes one step further to use a strong inner pointer inside the body of the block. This will ensure that the cell will not die in memory due to its reference count becoming 0 while the block is running. That situation is prevented in this way because the block itself will hold a strong reference to the cell only as long as the block is running. Once the block is done executing, the inner strong reference to the cell will die, and the cell can now die off as well.

Kinda complex, eh? Last important thing to note is this: the retain cycle could happen at any level. In our example, the cell points to a block, the block points to a cell. But it could happen anywhere. a point strongly to b, b points strongly to c, c points strongly to d, and d actually points strongly a. That too would cause a retain cycle. My co-worker says that as iOS developers, we try our best to remember the relationships and hierarchies, but then use Instruments to correct our shortcomings.

Hope that helps.

Great explanation tracicot!