This post is a look at how clang implements blocks and how this implementation leads to a number of strange behaviors including local variables that end up global, Objective-C objects allocated on the stack instead of the heap, C variables that behave like C++ references, Objective-C objects in non-Objective-C languages, copy methods that don't copy and retain methods that don't retain.
Blocks are addressable sections of code implemented inline (inside other functions). The inline-edness can be convenient but the real reason why blocks are different to regular functions and function pointers is that they can reference local variables from the scope of the function surrounding their implementation without the invoker of the block needing to know of the surrounding scope variables' existence.
A block is implemented internally using two pieces:
.text
segment of the executableThe compiled code lives in its own separate location and does not actually reside inside inside the code of its surrounding scope. In implementation, the code is a function like any other. If you run:
otool -tV MyCompiledExecutable
|
then you'll see your blocks appearing immediately after their surrounding functions with names like___surroundingFunction_block_invoke_21
.
So it is not the code which makes blocks special, it is the separate data structure. It is this data structure that I will focus on for the remainder of this post.
Clang's basic documentation on block implementations indicates that the data structure describing the block looks something like this:
struct
Block_literal
{
void
*isa;
int
flags;
int
reserved;
// is actually the retain count of heap allocated blocks
void
(*invoke)(
void
*, ...);
// a pointer to the block's compiled code
struct
Block_descriptor
{
unsigned
long
int
reserved;
// always nil
unsigned
long
int
size;
// size of the entire Block_literal
// functions used to copy and dispose of the block (if needed)
void
(*copy_helper)(
void
*dst,
void
*src);
void
(*dispose_helper)(
void
*src);
} *descriptor;
// Here the struct contains one entry for every surrounding scope variable.
// For non-pointers, these entries are the actual const values of the variables.
// For pointers, there are a range of possibilities (__block pointer,
// object pointer, weak pointer, ordinary pointer)
};
|
Of course, the reality is that this structure is never explicitly declared like this in clang. Clang is a compiler — a code generator — and the format of this structure is generated programmatically from the CodeGenFunction::BuildBlockLiteralTmp method.
Since the biggest difference between a function pointer and a block is the ability to use variables from the surrounding scope, it is interesting to look at what happens when a block does not reference anything in the surrounding scope.
Normally, the Block_literal
data appears on the stack (like a regular struct
would in its surrounding function). With no references to the surrounding scope, clang configures theBlock_literal
as a global block instead. This causes the block to appear in a fixed global location instead of on the stack (the flags
value has the BLOCK_IS_GLOBAL
flag set to indicate this at runtime but it's not immediately clear to me if this is ever used).
The implication of this is that global blocks are never actually copied or disposed, even if you invoke the functions to do so. This optimisation is possible because without any references to the surrounding scope, no part of the block (neither its code nor its Block_literal
) will ever change — it becomes a shared constant value.
If you're familiar with how Objective-C objects are declared, the isa
field in the Block_literal
above should be familiar — blocks are Objective-C objects. This may not seem strange in Objective-C but the reality is that even in pure C or C++, blocks are still Objective-C objects and the runtime support for blocks handles the retain
/release
/copy
behaviors for the block in an Objective-C messaging manner.
Clang uses the class names _NSConcreteStackBlock
and _NSConcreteGlobalBlock
to refer to the classes for block literals but in CoreFoundation projects, this will map onto NSStackBlock
andNSGlobalBlock
. If you copy an NSStackBlock
, it will return an NSMallocBlock
(indicating its changed allocation location).
The interesting point to note about NSStackBlock
is that it is a stack allocated Objective-C object. If you have ever tried to allocate an Objective-C object on the stack (not as a pointer but statically allocated) you'll know that the compiler normally forbids this.
The reason why blocks are placed on the stack by default is speed. In the common case where the lifetime of the block is less than that of the stack function that contains it, this is a very good optimisation.
The implication of stack blocks being allocated on the stack, is that a stack block cannot simply beretain
ed — it will become invalid once the function that contains it is popped from the stack. If you invoke retain
on a stack block, it will have no effect (the retain count of the block will remain at 1).
For this reason, if you need to return a block from a function or method, you must [[block copy] autorelease]
it, not simply [[block retain] autorelease]
it.
Scope variables used in a block are normally passed to the block by const
value (the compiler won't let you change the value but even if it did, the change wouldn't affect the value of the variable outside the block).
To alter this behavior, the type specifier __block
was added. Any variable declared __block
is passed by reference into the block (value on the outside will be changed after the block is invoked).
In the implementation, __block
variables are initially allocated on the stack but if any block which references them is copied, they are moved onto the heap (malloc
ed). This leads to the following strange situation...
int
(^function())()
{
__block
int
x =
0
;
int
(^block)() = ^{
x +=
1
;
return
x;
};
NSLog(
@"x's location is on the stack: %p"
, &x);
block = [[block
copy
]
autorelease
];
NSLog(
@"x's location is now on the heap: %p"
, &x);
return
block;
}
|
In this example, x's address changes when the copy is invoked. This is because when we declare a__block
variable, a pointer to the real variable is created and any attempt to use the variable dereferences it. When copy
is invoked, the location pointed to by the pointer changes to the new heap location, so any use of x
causes a dereference to this new location.
This makes __block
similar to to a reference parameter in C++ since C++ references are also transparently dereferenced pointers.
Copying a block doesn't really give you a copy of the block — if the block is already anNSMallocBlock
, a copy
simply increases the retain count of the block (this retain count is an internal reserved
field — the retainCount
returned from the object will remain at 1). This is perfectly appropriate since the scope of the block cannot change after it is created (therefore the block is constant) but it does mean that invoking copy
on a block is not the same thing as recreating it.
Assume the following code is in the same program as the previous example.
int
(^someBlock)() = counterBlock();
int
(^someBlockCopy)() = [[someBlock
copy
]
autorelease
];
int
(^anotherBlock)() = counterBlock();
|
The block returned from counterBlock()
counts the number of times that it is invoked by saving the count in the __block
variable x
.
In this example though, someBlock
and someBlockCopy
share the same x
variable — they are not actually separate copies. However, anotherBlock
does have its own separate x
value.
If you need a genuinely separate copy, recreate the block, don't copy it.
Blocks will retain
any NSObject
that they use from their enclosing scope when they are copied.
The biggest implication of this is that you must remember to avoid retain cycles if the block will be held beyond a simple stack lifetime.
A pointed out elsewhere, you can suppress this retain of NSObject
s by assigning the object to a__block
variable outside the block and only ever using the __block
variable inside the block.
You can also do the reverse of this and force a pointer that isn't an NSObject
derived class to be retained when copied. Do this by declaring the pointer with __attribute__((NSObject))
. Of course, the situations where you'd want to do this are exceedingly rare.
Blocks are very simple to use in the case where you declare one inline and immediately pass into another function but once you need to copy or hold onto a block for a while, there are a number of quirks, some of which I've covered in this post.
Sadly at this time, Apple's documentation on blocks is fairly basic and lacking in detail. This is what led me to start looking at clang's source code.
Of course, you don't need to stare at someone else's C++ code to learn about blocks. There are other sources of lighter, more approachable documentation on the topic. In addition to sources that I've already linked, there's also: