Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Should a block literal retain referenced heap-allocated blocks

Consider the following code:

// t included so block1 is a stack block. See [1] below
int t = 1;
SimpleBlock block1 = ^{ NSLog(@"block1, %d", t); };

// copy block1 to the heap
SimpleBlock block1_copied = [block1 copy];

// block2 is allocated on the stack, and refers to
// block1 on the stack and block1_copied on the heap
SimpleBlock block2 = ^{
    NSLog(@"block2");
    block1_copied();
    block1();
};
[block1_copied release];

// When the next line of code is executed, block2_copied is
// allocated at the same memory address on on the heap as
// block1_copied, indicating that block1_copied has been
// deallocated. Why didn't block2 retain block1_copied?

SimpleBlock block2_copied = [block2 copy];
block2_copied();
[block2_copied release];

Where, for completeness, SimpleBlock is defined by:

typedef void (^SimpleBlock)(void);

As indicated by the comment in the code, my tests (using both GCC 4.2 and LLVM 2.0) show that block1_copied is deallocated by the time [block2 copy] is called, yet according to the documentation that I have read [1,3], blocks are objective-c objects and blocks retain objective-c objects to which they refer [2] (in the non-instance variable case).

Additionally, note that when block2 is copied, its reference to block1 is also changed to a reference to a new copy of block1 (which is different than block1_copied), as expected, since blocks copy any blocks to which they refer [2].

So, what's going on here?

A) If blocks retain objective-c objects to which they refer and blocks are objective-c objects, why is block1_copied deallocated before block2 goes out of scope?

B) If blocks copy blocks to which they refer, and if sending -(id)copy to a heap-allocated block actually just increments its retain count, why is block1_copied deallocated before block2 goes out of scope?

C) If this is the expected behavior, where is the documentation that explains it?

[1] http://cocoawithlove.com/2009/10/how-blocks-are-implemented-and.html
[2] http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/bxVariables.html
[3] http://clang.llvm.org/docs/BlockLanguageSpec.txt

Footnote: In my tests, the result of running this code is an infinitely recursive call to block2_copied(), since block1_copied() had the same memory address as block2_copied.

like image 839
Andrew Hershberger Avatar asked Mar 26 '11 23:03

Andrew Hershberger


2 Answers

This is the specification. It is slightly stale right now and doesn't have the formalism of a normal spec. However, Blocks have been proposed in the C working group and a more formal specification has been discussed in that context.

Specifically, the spec says:

The Block_copy operator retains all objects held in variables of automatic storage referenced within the Block expression (or form strong references if running under garbage collection). Object variables of __block storage type are assumed to hold normal pointers with no provision for retain and release messages.

Thus, the behavior you are seeing is correct, though it is definitely a pitfall!

A block won't retain anything until the block is copied. Like blocks starting on the stack, this is largely a performance based decision.

If you were to change your code to:

SimpleBlock block2_copied = [block2 copy];
[block1_copied release];

It behaves as expected.

The static analyzer should catch that, but does not (Please file a bug).

like image 93
bbum Avatar answered Oct 02 '22 20:10

bbum


I note the same seems to happen with normal objects. This code:

NSNumber *foo = [[NSNumber alloc] initWithInt:42];
void(^block)(void) = ^{ NSLog(@"foo = %@", foo); };
[foo release];
NSNumber *foo2 = [[NSNumber alloc] initWithInt:43];
void(^block_copy)(void) = [block copy];
block_copy();

Prints "foo = 43"

It might be expected behavior. To quote Apple's documentation:

When you copy a block, any references to other blocks from within that block are copied if necessary

At the point block1_copy is released, block2 hasn't been copied yet.

like image 45
Anomie Avatar answered Oct 02 '22 21:10

Anomie