Using Blocks: Understanding the Memory Management Rules

11 Jul 2009, 18:09 PDT

Introduction

I just finished uploading PLBlocks 1.0-beta2, which adds support for iPhoneOS 2.2, iPhone 3GS armv7-optimized binaries, and host support for PPC systems (release announcement). This seems like a good time a good time to continue with my ad-hoc series on developing with Blocks (previous post).

I've fielded a number of questions regarding the memory management rules surrounding blocks; This article should help you understand the basic rules necessary to use blocks successfully.

I won't delve too deep into the describing the actual block implementation, as that's a topic well-covered by Clang's Block Implementation Specification Even if you don't have time to read the implementation specification, it's important to note that blocks are really just a few functions and structures implemented for you by the compiler, coupled with a runtime to help with managing setup and tear down. You could implement the same thing yourself in pure C -- but it would be so verbose as to not be useful.

The Memory Management Rules

Apple provides a set of straight-forward memory management rules for Objective-C. I've endeavoured to provide an equivalent set of rules for blocks in C and Objective-C.

This is the single fundamental memory management rule:

The following rules derive from the fundamental rule, or cope with edge cases:

Block Ownership: Copy vs. Retain

All blocks have a valid class pointer, and appear to be Objective-C objects. According to the standard Objective-C memory management, to assume ownership of an object you must simply retain it. However, blocks differ from the standard Objective-C objects in at least one fundamental and very important way: blocks defined within a function are allocated on the stack, and will persist only until your function returns.

This is why it is necessary to copy a block, rather than retain it: If the block is stack allocated, it must be copied to a heap allocation to persist beyond the lifetime of the current stack frame. Once copied, the block will be heap allocated and persist like any other standard Objective-C object.

Thus, to ensure that you do not attempt to maintain ownership of a stack allocated block, you must always copy a block if you wish to reference it past the lifetime of your current function. As an optimization, if a block has already been copied to the heap copying will simply increment the block's reference count.

It should also be noted that there is one very real benefit to stack allocation of blocks: they are very cheap, and unless copied, require no allocations or cleanup.

Captured Variables: Copy vs. Reference

By default, local variables (variables of the "auto" storage class) are captured by your block as const copies, while global variables are accessed directly. The runtime automatically handles reference counting of captured blocks and Objective-C objects.

To allow you modify a local variable (rather than its copy), Apple has added the __block storage specifier. Any __block variables are accessed by referenced from within a block. This is best explained by example:

__block int i = 0;
int j = 0;
executeBlock(^{
    // Increments the stack allocated 'i' variable by 1.
    i++;

// 'j' is a const copy, and can not be modified from within the block / j++ });

If you copy a block, any captured __block variables will also be copied to the heap.

Further Reading

I hope these brief explanations have provided a reasonable introduction to memory management with blocks. For additional details, you may wish to review Jim Dovey's post on the life-cycle of blocks, and join the PLBlocks Mailing List for further discussion.