Copying blocks (ie: copying them to instance variables) in Objective-C
Asked Answered
I

4

19

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!

Imes answered 17/4, 2010 at 16:19 Comment(1)
I could reproduce this in a test app: bitbucket.org/boredzo/block-retention-testDewie
C
30

You'll want to do this instead:

- (void) setupStoredBlock
{
    int salt = 42;
    m_storedBlock = Block_copy(^(int incoming){ return 2 + incoming + salt; });
}
Convocation answered 17/4, 2010 at 17:39 Comment(5)
This seems like a bug, either in Blocks or in the documentation. Blocks Programming Topics (developer.apple.com/mac/library/documentation/cocoa/Conceptual/…) 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.Dewie
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.” friday.com/bbum/2009/08/29/blocks-tips-tricks So, yeah, you do need to copy it—retain won't work here.Dewie
@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. :(Convocation
Dave DeLong: It does, but only for blocks on the heap (those that have previously been copied).Dewie
• Like all local variables, the block exists on the stack and will be popped from the stack like any other local variable. Block_Copy() copies the block from the stack onto the heap where all malloc instances exist. And like all new/copy operations, it returns a heap allocated object with a retain count of 1.Dextran
S
6

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
Stereotypy answered 21/4, 2010 at 5:4 Comment(1)
retain is only no-op on stack blocks. for heap blocks, they do retainAshlaring
D
6

• Like all local variables, a non-static block exists on the stack and will be popped from the stack, like any other local variable which has not been declared static.

• Block_copy() copies the block from the stack onto the heap, where all malloc instances exist. And like all new/copy methods, Block_copy() returns a heap allocated object with a retain count of 1. A block is an objectiveC object but doesNot conform like a normal object. Therefore, there should be no difference between Block_Release() and the objective release method.

• This example uses the copy method of a block instance. Because assigning the result of a Block_copy() to an id requires a type cast that I doNot want to get wrong. The copy method allows the block variable to be assigned directly to an id.

 - (void) setupStoredBlock
{
    int zStackLocalVariable = 42;
    iHeapAllocatedVariable = [^int(int aMore){ return zStackLocalVariable + aMore; } copy];
}

• To declare an object static is to require it to be physically allocated with the code itself. A block which is declared static is compiler prohibited from accessing variables outside of its own scope. Due to the requirements of a static block declaration, I assume that the block on the stack is somehow different from the block which is in the heap.

• A block is an objective c object whose class whose class name and other associated information I have not yet attempted to retrieve, but, like Protocol, Object and other hidden objectiveC classes, it does not conform to NSObject. Like all objectiveC objects, however, it must conform to retain/release. ARC extends retain/release equivalencies into Core Foundation objects as well, and probably, if not now, then eventually, into malloc/free allocations.

• I await the true motivation for a thorough exploration of mikeash.com, as apple likes to keep us all on some hyper-theoritical plane of little physical significance, even though all that is significant is physical.

ARC and blocks also discussed here

Dextran answered 21/1, 2013 at 2:45 Comment(2)
Nice answer. For bullet point two, would it be accurate to say a block is an Objective-C object but isn't an NSObject subclass? Or, what do you mean by 'not conform like a normal object'? Also, I'm having trouble understanding the varying physical size, and the "blocks may only access its own parameters..." phrase is not clear to me either. Is there a reference I can look at, or can you put it a different way?Malcom
"but also all outside-the-block stack variables used in the block itself" No, the outside variables are copied when the block is created, not when it is copied.Hydrolyze
N
4

There were a very nice presentation on that subject on recent WWDC (2010). It described how blocks were implemented and why you need to use Block_copy. You can download a movie from the presentation at: http://developer.apple.com/itunes/?destination=adc.apple.com.4092414566 The movie is called: "Advanced Objective-C and garbage collection"

Neptunian answered 2/11, 2010 at 20:23 Comment(1)
Actually the video recommends using the copy method, not Block_copy. Quote from around 23:50: "There are also C functions for copying and deleting blocks (i.e. Block_copy). If you're writing pure C code, you can use those C functions instead. But you should prefer to use the methods instead of the functions, especially if you're using garbage collection."Kalk

© 2022 - 2024 — McMap. All rights reserved.