Why do NSBlocks need to be copied for storage in containers?

- (void) addABlock { void (^aBlock)(void) = ^() { [someObject doSomething]; }; [self.myMutableArray addObject: aBlock]; // Oops.. [self.myMutableArray addObject: [aBlock copy]]; // works fine } 

In the simplified example above, I see undefined behavior if the block copy fails. This case is specifically indicated in the Apple ARC migration guide.

The part I don’t understand is why I need to manually call a copy. A block is created on the stack, so block_copy must be executed - this is clear. NSArray does not cause copying, but it must cause the persistence of objects that are added. So why did [NSBlock save] just go to [NSBlock copy]?

http://developer.apple.com/library/mac/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html

http://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/

+3
objective-c block objective-c-blocks
Jun 29 '13 at 22:45
source share
2 answers

Update

Although the Apple documentation says:

Blocks "just work" when you push blocks up on the stack in ARC mode, for example, in return. You no longer need to call Block Copy. You still need to use [^{} copy] when passing the "down" stack to arrayWithObjects: and other methods that save.

it is no longer needed to manually call copy in a block when adding it to a container. The absence of an automatic copy in this case was considered a compiler error and fixed in llvm long time ago.

"We believe this is a compiler error, and it has been fixed for several months in the open source clang repository."

( John McCall , LLVM Developer)

I personally tested this in Xcode 5 using the latest Apple LLVM 5.0 compiler.

 - (NSArray *)blocks { NSMutableArray *array = [NSMutableArray array]; for (NSInteger i = 0; i < 3; i++) { [array addObject:^{ return i; }]; } return array; } - (void)test { for (NSInteger (^block)() in [self blocks]) { NSLog(@"%li", block()); } } 

The above example prints correctly

 0 1 2 

in ARC, and it resets from EXC_BAD_ACCESS in MRC.

Please note that this - finally - is consistent with the llvm document, which states

whenever these semantics require saving the value of the type of the pointer block, it has the Block_copy effect

means that whenever ARC should save a pointer, and that pointer turns out to be a type of pointer block, Block_copy will be called instead of retain .




Original answer

The part I don’t understand, so I need to manually call the copy.

Blocks are one of the few examples of Objective-C objects allocated on the stack (for performance reasons), so when you return from a method call that you lose due to the failure of the current stack frame.

Sending copy in the stack block will call Block_copy on it, and it will move it to the heap, which allows you to save a valid link to the block.

So why [NSBlock retain] just doesn't access [NSBlock copy]

This violates the usual retain semantics, which are supposed to return the object itself with an increased save counter. Since incrementing the hold counter on the stack block does not make any sense, calling retain on the stack block has no effect.

Apple could implement it in different ways, as you suggest, but they preferred to stick to the most general contracts for memory management practices.

As an additional link to the blocks, you might want to take a look at this great @bbum blog post. This is pre-ARC, but most concepts have not changed.

+14
Jun 29 '13 at 22:47
source share

In ARC, you no longer need to manually copy blocks in this case or in most others. According to clang ARC block documentation

With the exception of saving the __strong parameter as part of the initialization of the variable or reading the __weak variable, whenever these semantics require saving the value of the type of the block pointer, it has the Block_copy effect. The optimizer can delete such copies when it sees that the result is used only as an argument to the call.

In other words, most of the time saving a block has the Block_copy effect , as you expected, it should be like that. In particular, when a block is added to the collection, it is copied! (More precisely, it is already on the heap.) Here is an example code that shows that it is.

 #import <Foundation/Foundation.h> int main(int argc, char *argv[]) { @autoreleasepool { NSMutableArray *arr = [[NSMutableArray alloc] init]; int counter = 0; int total = 5; for (int i = 0; i < total; i++) { void (^block)(void) = ^{ NSLog(@"in this block, counter is %d", counter); }; [arr addObject:block]; counter += 1; } for (int i = 0; i < total; i++) { void (^block)(void) = arr[i]; block(); } } } 

If you use the latest Xcode (4.6.3 (4H1503)) with the default compiler (Apple LLVM 4.2 compiler), then this will be

 in this block, counter is 0 in this block, counter is 1 in this block, counter is 2 in this block, counter is 3 in this block, counter is 4 

If these blocks were not copied to the heap, you (probably this behavior is undefined) see

 in this block, counter is 4 in this block, counter is 4 in this block, counter is 4 in this block, counter is 4 in this block, counter is 4 

since the pointers added to the array all pointed to the allocated stack (not copied) block, which - during the execution of the blocks - fixed the value counter 4 .

In particular, this is the same behavior that you get if you disable ARC. Even if you call retain on blocks before adding them to the array

 [arr addObject:[block retain]]; 

you will still get the same ("broken") output illustrating that this is ARC behavior, not saving behavior at all .

Note:

In two places where saving ARC does not affect Block_copy,

(1) saved as part of initializing the __strong parameter __strong

Immediately after entering a function (or method), if an object is passed to this function (or method), this object will be saved (there is a strong reference to the object in the stack frame) and will be released when the function (or method) (significant reference to the object is freed )

This is as true for blocks as it is for any other object. This phrase in the clang documentation means that although the block is saved in this case, the block will not be copied by this save.

(2) saved as part of reading __weak variable

Similarly, when the __weak variable __weak read into the __strong variable, a strong reference to the object is created and the object is saved.

This also applies to blocks. Blocks that are sent are saved this way (as a result of reading the __weak link to the __strong link), however, they are not copied.

Cases where one of these two exceptions cause problems are rare. In general, you do not need to worry about copying your blocks.

+1
Jul 01 '13 at 18:58
source share



All Articles