Reputation:
I have some code that essentially boils down to this:
-(void)doSomethingWithBlock:(BlockTypedef)block
{
[Foo doSomethingElseWithBlock:^() {
block();
}];
}
Foo doSomethingElseWithBlock:
calls Block_copy
and Block_release
on the block that it receives. Is this also necessary at the outer scope, or will the inner Block_copy
handle this?
Upvotes: 12
Views: 2212
Reputation: 109
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
Upvotes: 3
Reputation: 299623
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.)
Upvotes: 2
Reputation: 118761
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.
Upvotes: 8