I have some code that essentially boils down to this:
-(void)doSomethingWithBlock:(BlockTypedef)block
{
[Foo doSomethingElseWithBlock:^() {
block
I quote the Blocks Programming Topics guide on Apple's developer documentation site:
When you copy a block, any references to other blocks from within that block are copied if necessary—an entire tree may be copied (from the top). If you have block variables and you reference a block from within the block, that block will be copied.
When you copy a stack-based block, you get a new block. If you copy a heap-based block, however, you simply increment the retain count of that block and get it back as the returned value of the copy function or method.
Yes, this is safe. You don't need to make a copy. At the time that -[Foo doSomethingElseWithBlock:]
makes a copy of your literal block, it will copy the inner block to the heap.
I wrote some test code to prove to myself that this happens; see how printer
(used only in block1
) is copied from the stack to the heap at the time that Block_copy(block2)
is called.
#include <Block.h>
#include <dispatch/dispatch.h>
#include <stdio.h>
typedef void (^void_block)();
class ScopedPrinter {
public:
ScopedPrinter() {
printf("construct %p\n", this);
}
ScopedPrinter(const ScopedPrinter& other) {
printf("copy %p <- %p\n", this, &other);
}
~ScopedPrinter() {
printf("destroy %p\n", this);
}
};
void_block invoke(void_block other) {
printf("other %p\n", (void*)other);
void_block block2 = ^{
printf("other %p\n", (void*)other);
other();
};
printf("block2 created\n");
block2 = Block_copy(block2);
printf("block2 copied\n");
return block2;
}
void_block make_block() {
ScopedPrinter printer;
printf("printer created\n");
void_block block1 = ^{
printf("block1 %p\n", &printer);
};
printf("block1 created\n");
return invoke(block1);
}
int main() {
void_block block = make_block();
block();
Block_release(block);
return 0;
}
Transcript:
construct 0x7fff6a23fa70
printer created
copy 0x7fff6a23fa50 <- 0x7fff6a23fa70
block1 created
other 0x7fff6a23fa30
block2 created
copy 0x10a700970 <- 0x7fff6a23fa50
block2 copied
destroy 0x7fff6a23fa50
destroy 0x7fff6a23fa70
other 0x10a700950
block1 0x10a700970
destroy 0x10a700970
The inner Block_copy()
isn't really relevant here. What you want to keep track of is whether a given block lives on the stack or on the heap. Consider this code based on your example:
@interface Foo : NSObject
@end
@implementation Foo
typedef void(^BlockTypedef)(void);
+(void)doSomethingElseWithBlock:(BlockTypedef)block
{
NSLog(@"block=%@", block);
BlockTypedef myBlock = Block_copy(block);
NSLog(@"myBlock=%@", myBlock);
myBlock();
Block_release(myBlock);
}
+(void)doSomethingWithBlock:(BlockTypedef)block
{
[Foo doSomethingElseWithBlock:^() {
block();
}];
}
@end
int main (int argc, const char * argv[])
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
int i = 3;
BlockTypedef block = ^{ printf("i=%d\n", i); };
NSLog(@"block=%@", block);
[Foo doSomethingWithBlock:block];
block();
NSLog(@"block=%@", block);
[pool drain];
return 0;
}
This should be ok, but block
and myblock
are different kinds of blocks. block
is a stack block and has the scope of the calling stack. It will exist until main()
exits. myblock
is a malloc (heap) block, and will exist until it is released. You need to make sure that you don't try to take a non-copied reference to block
and use it after the stack is done. You can't stick block
in an ivar without copying it.
Joachim Bengtsson has the best write-up of this that I know of. @bbum has also written about it. (If bbum wanders in here and says I'm an idiot about this, then listen to him, but I think I'm right here.)