views:

272

answers:

3

I'm trying to understand blocks. I get how to use them normally, when passed directly to a method. I'm interested now in taking a block, storing it (say) in an instance variable and calling it later.

The blocks programming guide makes it sound like I can do this, by using Block_copy / retain to copy the block away, but when I try to run it I crash my program.

- (void) setupStoredBlock
{
    int salt = 42;
    m_storedBlock = ^(int incoming){ return 2 + incoming + salt; };
    [m_storedBlock retain];
}

I try to call it later:

- (void) runStoredBlock
{
    int outputValue = m_storedBlock(5);
    NSLog(@"When we ran our stored blockwe got back: %d", outputValue);
    [m_storedBlock release];
}

Anyone have any insights? (Or, is there something I'm not getting with blocks?)

Thank you very much!

+4  A: 

You'll want to do this instead:

- (void) setupStoredBlock
{
    int salt = 42;
    m_storedBlock = Block_copy(^(int incoming){ return 2 + incoming + salt; });
}
Dave DeLong
This seems like a bug, either in Blocks or in the documentation. Blocks Programming Topics (http://developer.apple.com/mac/library/documentation/cocoa/Conceptual/Blocks/Articles/bxUsing.html#//apple_ref/doc/uid/TP40007502-CH5-SW2) says `retain` should work. In my testing, retaining a block did behave as normal (i.e., it does not return a copy), but when assigning the block to an instance variable, calling it from a later message does crash as described in the question. Changing the retention to a copy (either using `copy` or `Block_copy`) fixes the crash.
Peter Hosey
Ah. No bug. Here's bbum on his blog: “For a Block that hasn’t been copied, -retain doesn’t make sense. It does nothing. It could have been implemented to return a copy of the Block, but that would have been a bifurcation of the -retain contract beyond acceptable.” http://www.friday.com/bbum/2009/08/29/blocks-tips-tricks/ So, yeah, you do need to copy it—`retain` won't work here.
Peter Hosey
@Peter yeah, it's an interesting setup. I wish that `-retain` had the same semantics with blocks that we're used to with objects, but there are the occasional gotchas, like this. :(
Dave DeLong
Dave DeLong: It does, but only for blocks on the heap (those that have previously been copied).
Peter Hosey
+1  A: 

Copy a block when you want it to stay around. Autorelease or release it when you're through with it. Retain it if you need a long way to spell /* NOP */.

@interface Foo : FooSuper {}
@property(copy) int (^storedBlock)(int);
@end

@implementation Foo
@synthesize storedBlock = mStoredBlock;

- (void)setupStoredBlock {
    self.storedBlock = ^{/*...*/};
    // or: mStoredBlock = [^{/*...*/} copy];
    // but this simple implementation violates the atomicity contract
}

- (void)runStoredBlock {
    int result = self.storedBlock(5);
    NSLog(@"%s: result = %d", __func__, result);
}
@end
Jeremy W. Sherman