Question

I will preface this question by stating that what I am about to ask is for educational and possibly debug purposes only.

How are block objects created internally in the Objective C runtime?

I see the hierarchy of classes that all represent various block types, and the highest superclass in the hierarchy, below NSObject, is NSBlock. Dumping for class data shows that it implements the + alloc, + allocWithZone:, + copy and + copyWithZone: methods. None of the other block subclasses implement these class methods, which leads me to believe, perhaps mistakenly, that NSBlock is responsible for block handling.

But these methods seem not to be called at any point during a block's life time. I exchanged implementations with my own and put a breakpoint in each, but they never get called. Doing similar exercise with NSObject's implementations gives me exactly what I want.

So I assume blocks are implemented in a different manner? Anyone can shed a light on how this implementation works? Even if I cannot hook into the allocation and copying of blocks, I would like to understand the internal implementation.

Was it helpful?

Solution

tl;dr

The compiler directly translates block literals into structs and functions. That's why you don't see an alloc call.


discussion

While blocks are full-fledged Objective-C objects, this fact is seldom exposed in their use, making them quite funny beasts.

One first quirk is that blocks are generally created on the stack (unless they are global blocks, i.e. blocks with no reference to the surrounding context) and then moved on the heap only if needed. To this day, they are the only Objective-C objects that can be allocated on the stack.

Probably due to this weirdness in their allocation, the language designers decided to allow block creation exclusively through block literals (i.e. using the ^ operator). In this way the compiler is in complete control of block allocation.

As explained in the clang specification, the compiler will automatically generate two structs and at least one function for each block literal it encounters:

  • a block literal struct
  • a block descriptor struct
  • a block invoke function

For instance for the literal

^ { printf("hello world\n"); }

on a 32-bit system the compiler will produce the following

struct __block_literal_1 {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(struct __block_literal_1 *);
    struct __block_descriptor_1 *descriptor;
};

void __block_invoke_1(struct __block_literal_1 *_block) {
    printf("hello world\n");
}

static struct __block_descriptor_1 {
    unsigned long int reserved;
    unsigned long int Block_size;
} __block_descriptor_1 = { 0, sizeof(struct __block_literal_1), __block_invoke_1 };

(by the way, that block qualifies as global block, so it will be created at a fixed location in memory)

So blocks are Objective-C objects, but in a low-level fashion: they are just structs with an isa pointer. Although from a formal point of view they are instances of a concrete subclass of NSBlock, the Objective-C API is never used for allocation, so that's why you don't see an alloc call: literals are directly translated into structs by the compiler.

OTHER TIPS

As described in other answers, block objects are created directly in global storage (by the compiler) or on the stack (by the compiled code). They aren't initially created on the heap.

Block objects are similar to bridged CoreFoundation objects: the Objective-C interface is a cover for an underlying C interface. A block object's -copyWithZone: method calls the _Block_copy() function, but some code calls _Block_copy() directly. That means a breakpoint on -copyWithZone: won't catch all of the copies.

(Yes, you can use block objects in plain C code. There's a qsort_b() function and an atexit_b() function and, uh, that might be it.)

Blocks are basically compiler magic. Unlike normal objects, they are actually allocated directly on the stack — they only get placed on the heap when you copy them.

You can read Clang's block implementation specification to get a good idea what goes on behind the scenes. To my understanding, the short version is that a struct type (representing the block and its captured state) and a function (to invoke the block) are defined, and any reference to the block is replaced with a value of the struct type that has its invoke pointer set to the function that was generated and its fields filled in with the appropriate state.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top