28

I've just stumbled over the following SO topic: Why should we copy blocks rather than retain? which has the following sentence:

However, as of iOS 6 they are treated as regular objects so you don't need to worry.

I was really confused by this assertion that is why I am asking: does this assertion really imply that Objective-C developers do not need to

@property (copy) blockProperties or

[^(...){...) {} copy]

to copy blocks and their contents from stack to heap anymore?

I hope the description I've made is clear.

Please, be verbose.


Similar questions

Under ARC, are Blocks automatically copied when assigned to an ivar directly?.

Community
  • 1
  • 1
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
  • 1
    Blocks have ALWAYS been objects. And OS_OBJECT_USE_OBJC has nothing to do with blocks. Those are for things like dispatch queues. – newacct Apr 29 '14 at 06:04

3 Answers3

49

ARC will copy the block automatically. From clang's Objective-C Automatic Reference Counting documentation:

With the exception of retains done as part of initializing a __strong parameter variable or reading a __weak variable, whenever these semantics call for retaining a value of block-pointer type, it has the effect of a Block_copy. The optimizer may remove such copies when it sees that the result is used only as an argument to a call.

So blocks used only as arguments to function or method calls may remain stack blocks, but otherwise anywhere that ARC retains the block it will copy the block. This is implemented by the compiler emitting a call to objc_retainBlock(), the implementation for which is:

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

It is still a good idea to declare block properties as having copy semantics since a block assigned to a strong property will in fact be copied. Apple recommends this as well:

You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior.

Note that since this copy-on-retain feature is provided by ARC it is dependent only on ARC or ARCLite availability and does not otherwise require a particular OS version or OS_OBJECT_USE_OBJC.

Matt Stevens
  • 13,093
  • 3
  • 33
  • 27
  • 2
    "ARC will copy the block automatically." Right, *except* for when it's used only as the argument to a call. – newacct Apr 29 '14 at 06:13
  • 2
    @newacct As an optimization, yes. If a block is used only as an argument to a call then it doesn't outlive its scope and doesn't need to be copied. If the receiver of such an argument uses it in a way that requires the block to be retained ARC will again copy the block automatically. – Matt Stevens Apr 29 '14 at 12:02
  • @MattStevens: "If the receiver of such an argument uses it in a way that requires the block to be retained ARC will again copy the block automatically." Not if the receiver doesn't know it's a block. – newacct Apr 29 '14 at 17:58
  • 2
    Can you show an example where the block copy does not occur? – Matt Stevens Apr 29 '14 at 21:30
  • @MattStevens: In the case of `[myMutableArray addObject: ^ { NSLog(@"%d", i); }];`, the ARC specification does not guarantee that the block is copied. – newacct May 01 '14 at 06:50
  • 2
    @newacct I took that optimization to refer to parameters of block pointer type, but I see what you mean. In practice the compiler emits a call to `objc_retainBlock()` on the block argument for your scenario, so ARC copies the block automatically there as well. – Matt Stevens May 01 '14 at 15:03
10

Edit:

It turned out the examining the addresses of the "captured" variables are difficult to interpret, and not always appropriate to figure out whether a Block has been copied to the heap or still resides on the heap. Although the Specification of Blocks given here BLOCK IMPLEMENTATION SPECIFICATION, will sufficiently describe the facts, I try a completely different approach:

What is a Block anyway?

This is a summary of the official Specification BLOCK IMPLEMENTATION SPECIFICATION:

A Block exists of code (like a function) and a structure containing several peaces of data, flags and function pointers AND a variable length section for "captured variables".

Note that this structure is private and implementation defined.

A Block may be defined in function scope, where this structure is created in stack local memory, or it may be defined in global or static scope, where the structure is created in static storage.

A Block may "import" other Block references, other variables and __block modified variables.

When a Block references other variables, they will be imported:

  • A stack local (automatic) variable, will be "imported" by means of making a "const copy".

  • A __block modified variable will be imported by means of assigning a pointer the address of that variable enclosed in another structure.

  • Global variables will be simply referenced (not "imported").

If a variable will be imported, the "captured variable" lives in the aforementioned structure in the variable length section. That is, the "counterpart" of the automatic variable (which lives outside the block) has a storage within the block's structure.

Since this "captured variable" is read only, the compiler can apply a few optimizations: for example we really only need one instance of the captured variable on the heap, if we ever need a copy of the block.

The value of captured variables will be set when the block literal expression will be evaluated. This also implies, that the storage of the captured variables must be writable (that is, no code section).

The lifetime of captured variables is that of a function: each invocation of that block will require a new copy of these variables.

Captured variables in Blocks which live on the stack are destroyed when the program leaves the compound statement of the block.

Captured variables in Blocks which live on the heap are destroyed when the block will be destroyed.

According the Block API, versions 3.5:

Initially, when a block literal is created, this structure will exist on the stack:

When a Block literal expression is evaluated the stack based structure is initialized as follows:

  1. A static descriptor structure is declared and initialized as follows:

    a. The invoke function pointer is set to a function that takes the Block structure as its first argument and the rest of the arguments (if any) to the Block and executes the Block compound statement.

    b. The size field is set to the size of the following Block literal structure.

    c. The copy_helper and dispose_helper function pointers are set to respective helper functions if they are required by the Block literal.

  2. A stack (or global) Block literal data structure is created and initialized as follows:

    a. The isa field is set to the address of the external _NSConcreteStackBlock, which is a block of uninitialized memory supplied in libSystem, or _NSConcreteGlobalBlock if this is a static or file level Block literal.

    b. The flags field is set to zero unless there are variables imported into the Block that need helper functions for program level Block_copy() and Block_release() operations, in which case the (1<<25) flags bit is set.

Please note that this is for Block literals.

According the Objective-C Extensions to Blocks, the compiler will treat Blocks like objects.

Observations

Now, it's difficult to craft test code which proves these assertions. Thus, it seems better to use the debugger and set symbolic breakpoints at relevant functions

  • _Block_copy_internal and

  • malloc (which should be enabled only after the first breakpoint has been hit)

and then run suitable test code (like the snippets below) and examine what happens:

In the following code snippet, we create a Block literal and pass it through as parameter to a function which calls it:

typedef void (^block_t)(void);

void func(block_t block) {
    if (block) {
        block();
    }
}

void foo(int param)
{
    int x0 = param;
    func(^{
        int y0 = x0;
        printf("Hello block 1\n");
        printf("Address of auto y0: %p\n", &y0);
        printf("Address of captured x0: %p\n", &x0);
    });
}  

The output is as follows:

Hello block 1
Address of auto y0: 0x7fff5fbff8dc
Address of captured x0: 0x7fff5fbff940

The address of the "captured" variable x0 strongly indicates that it lives on the stack.

We also have set a breakpoint at _Block_copy_internal - however, it won't be hit. This indicates, that the Block has not been copied onto the heap. Another proof can be made with Instruments, which doesn't show allocations in function foo.

Now, if we create and initialize a block variable, it seems, the Block data structure of the original Block literal - which is initially created on the stack, will be copied onto the heap:

int capture_me = 1;
dispatch_block_t block = ^{ int y = capture_me; };

enter image description here

This above copies the block which has been initially created on the stack. This may simply happen due to ARC and the fact, that we have a block literal on the right hand, and the block variable block on the left hand will be assigned the block literal - which results in a Block_copy operation. This makes Blocks appear much like normal Objective-C objects.

The Allocations traced with Instruments in this similar code below

void foo(int param)
{
    dispatch_queue_t queue = dispatch_queue_create("queue", 0);

    int x0 = param;
    dispatch_block_t block = ^{
        int y0 = x0;
        printf("Hello block 1\n");
        printf("Address of auto y0: %p\n", &y0);
        printf("Address of captured x0: %p\n", &x0);
    };

    block();
}    

show, that the Block will be indeed copied:

enter image description here

Noteworthy

  • When a block doesn't capture any variables, it is like a normal function. Then it makes sense to apply an optimization, where a Block_copy operation does actually nothing, since there is nothing to copy. clang implements this with making such blocks a "global block".

  • When sending copy to a block variable and assigning the result to another block variable, e.g.:

    dispatch_block_t block = ^{
        int y0 = capture_me;
    };
    dispatch_block_t otherBlock = [block copy];
    

    copy will be a quite cheap operation, since the block block has already allocated storage for the block's structure which can be shared. Thus, copy doesn't need to allocate storage again.

Back to the question

To answer the question if we need to explicitly copy a block in certain circumstances, for example:

@property (copy) block_t completion

[^{...} copy]

Well, once the block has been copied, no matter when, and will then be assigned the target variable (a Block variable) - we should be always safe without explicitly copying the block, since it is already in the heap.

In case of the block property, it should be safe if we would simply wright:

@property dispatch_block_t completion;

and then:

foo.completion = ^{ x = capture_me; ... };

This should be safe since assigning a block literal (which lives on the stack) the underlying block variable _completion, will copy the block onto the heap.

Nonetheless, I would still recommend to use attribute copy - since it is still suggested by the official documentation as best practice, and also supports older APIs where Blocks don't behave like normal Objective-C objects and where there is no ARC.

Existing system APIs will also take care of copying a Block if required:

dispatch_async(queue, ^{int x = capture_me;});

dispatch_async() will make the copy for us. So, we don't have to worry.

Other scenarios are more subtle:

dispatch_block_t block;
if (condition) {
    block = ^{ ... };
}
else {
    block = ^{ ... };
}
dispatch_sync(queue, block);

But actually, this is safe: The block literal will be copied and assigned the block variable block.

This example might look even scary:

int x0 = param;
NSArray* array = [NSArray arrayWithObject:^{
    int y0 = x0;
    printf("Hello block 1\n");
}];

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), queue, ^{
    block_t block = array[0];
    block();
});

But it seems, the Block literal will be correctly copied as an effect of assigning the argument (the Block) to the parameter (an id) in method arrayWithObject: which copies the Block literal onto the heap:

enter image description here

Additionally, NSArray will retain the object passed as parameter in method arrayWithObject:. This causes another call to Block_copy()however, since the Block is already onto the heap, this call does not allocate the storage.

This indicates, that a "retain" message sent to a Block literal somewhere in the implementation of arrayWithObject: would also indeed copy the Block.

So, when do we actually need to explicitly copy a Block?

A block literal - e.g. the expression:

^{...}

will create the block structure on the stack.

So, we actually need to make a copy only in cases where we need the Block on the heap and when this doesn't happen "automatically". However, there are virtually no scenarios where this is the case. Even in cases where we pass a Block as a parameter to a method which has no idea that it is a Block literal and requires a copy first (e.g.: arrayWithObject:), and possibly the receiver sends just a retain message to the object given in the parameter, the Block will be copied onto the heap first.

Examples where we might explicitly use copy, is where we do not assign a Block literal a block variable or an id, and thus ARC cannot figure out that it has to make copy of the block object or has to send "retain".

In this case, the expression

[^{...} copy];  

will yield an autoreleased Block whose structure resides on the heap.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67
  • 1
    You are printing address of the stack block variable, by doing `&block`, not the block itself. Since blocks are Obj-C objects, they are pointers. – coverback Apr 28 '14 at 11:34
  • @coverback I think, I need to clarify a few things. Your modified answer is actually not appropriate to prove anything :) I'll edit may answer to make this clear, please be patient :) – CouchDeveloper Apr 28 '14 at 11:35
  • Meanwhile my +1 to @coverback's comment. – Stanislav Pankevich Apr 28 '14 at 11:55
  • To emphasize the critical aspects of your test code: your addresses of __auto y0, y1, y2, y3__ variables only __seem to live on a heap__. These are just the similar addresses that confuse you: addresses like __0x1000b7ddc__ are __the local variables of the stack frames of another threads__ when you are dispatching to these threads by GCD asynchronously! Add some local variable inside dispatch_asyncs itself(not within any of two blocks!) and you'll see that it produces the numbers which are similar to the ones of heap memory - but they are just similar. It is a stack of these threads, not heap! – Stanislav Pankevich Apr 28 '14 at 15:23
  • @Stanislaw I've completely rewritten the answer and tried to clarify the bits. The addresses of the "captured" variables (x0, x1) are not always a clear indication whether the block has been copied or not. The addresses of the block local variables (y0, y1) are stack addresses in the corresponding thread. To be sure whether a block has been copied or not really requires to look at the disassembly. In the new answer, I used Instruments and the debugger to trace Block_copy() and allocations. – CouchDeveloper Apr 28 '14 at 20:58
  • "But it seems, the Block literal will be correctly copied as an effect of assigning the argument (the Block) to the parameter (an id)" This claim is not justified anywhere. "This indicates, that a "retain" message sent to a Block literal somewhere in the implementation of arrayWithObject: would also indeed copy the Block." No it doesn't. – newacct Apr 29 '14 at 06:17
  • @CouchDeveloper, thanks for this detailed exploration. I need some time to read carefully your answer as well as Block Implementation Specification. – Stanislav Pankevich Apr 29 '14 at 08:12
  • @CouchDeveloper thanks for the detailed exploration! I am researching on blocks and the experiment is super useful. – Oliver Hu Mar 20 '17 at 21:59
1

Mine original answer was wrong. Edited answer is not an answer, but more of a "it's indeed a good question" thing.

Please, see Matt's answer for real references why stuff is the way it is.

After testing the following code on iOS7:

int stackVar;
void (^someBlock)() = ^(){};
NSLog(@"int: %x\nblock: %x,\ncopied: %x", (unsigned int)&stackVar, (unsigned int)someBlock, (unsigned int)[someBlock copy]);

I got this:

int: bfffda70
block: 9d9948
copied: 9d9948

Which pretty much means that blocks created and assigned into stack variables are actually already on a heap and copying them doesn't affect anything.

This, though, is not backed up by any official source, as they still state that blocks are created on stack on need to be copied "when passing down".


Part of the answer before I tested, stating which docs are contradicted by the example.

Document about transition to ARC states:

Blocks “just work” when you pass blocks up the stack in ARC mode, such as in a return. You don’t have to call Block_copy any more. You still need to use [^{} copy] when passing “down” the stack into arrayWithObjects: and other methods that do a retain.

And docs about blocks and properties says:

You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope.

coverback
  • 4,413
  • 1
  • 19
  • 31
  • thanks for the answer and sorry: I will accept the following two kinds of answers: references to some authoritative links of 2013-2014 years and/or any good crash example on iOS6-7, Mac OSX10.8-9 runtimes. – Stanislav Pankevich Apr 28 '14 at 09:22
  • "While none of the docs state explicitly what has changes and what has not, one can conclude from these bits are pieces that blocks need to be copied in properties and when they are passed to a different stack scope." This is exactly the point I want to clarify! Of course, I am aware of these official links, I am just can't be sure that they really reflect current situation. – Stanislav Pankevich Apr 28 '14 at 10:09
  • @Stanislaw Coverback is right. The important bits are: 1. A Block will be initially created on the stack (as opposed to "normal" Objective-C objects, which are *always* created on the heap). 2. When setting a property whose value is a Block, we *really* need it to be on the heap (like other "normal" objects). Otherwise we would keep a dangling pointer in the ivar. The default behavior for the setter of a property whose value is a retainable pointer is "retaining it". Thus, we need to set the attribute `copy`. – CouchDeveloper Apr 28 '14 at 10:12
  • @Stanislaw Note: using attribute `copy` is always safe, but not always necessary in cases where the Block is already on the heap. (The copy is actually just a "retain" when the Block is on the heap already). – CouchDeveloper Apr 28 '14 at 10:14
  • @CouchDeveloper, blocks currently seem to be created on a heap. How would you prove that they are still created on a stack? Please, do. – Stanislav Pankevich Apr 28 '14 at 10:15
  • @coverback, I did a bunch of tests - they show me that I am right. I am just not sure whether I create enough __robust__ tests. – Stanislav Pankevich Apr 28 '14 at 10:16
  • @Stanislaw Please verify your tests ;) I also tested this, and it works as stated in the docs: "blocks start out on the stack." ;) – CouchDeveloper Apr 28 '14 at 10:17
  • @CouchDeveloper, please do craft your test and post in as an answer. I will accept it as a correct answer if it will demonstrate what you are stating on >=iOS6 or >=10.8. I am extremely interested in such counter example! – Stanislav Pankevich Apr 28 '14 at 10:19
  • @CouchDeveloper, I've seen some of your other cool answers here on SO so I would really appreciate such counter example. By the way I've posted this question on #macdev channel but no one have stepped in to reveal this confusion yet. – Stanislav Pankevich Apr 28 '14 at 10:25
  • @Here is a reference, stating "The initial Apple implementation does in fact start __block variables on the stack and migrate them to the heap only as a result of a Block_copy() operation." [LANGUAGE SPECIFICATION FOR BLOCKS](http://clang.llvm.org/docs/BlockLanguageSpec.html#block-variable-declarations) – CouchDeveloper Apr 28 '14 at 10:25
  • @Stanislaw The answer to your question may be also answered here: http://stackoverflow.com/questions/18157360/does-dispatch-async-copy-internal-blocks/18160177#18160177 -- although the question is slightly different (even more subtle). – CouchDeveloper Apr 28 '14 at 10:28
  • @CouchDeveloper, the irony is in the fact that I've opened this topic after exhaustive testing I've done using exactly this your answer and your test code. I can't confirm that blocks are initially stack objects. Do you? – Stanislav Pankevich Apr 28 '14 at 10:30
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/51588/discussion-between-stanislaw-and-couchdeveloper) – Stanislav Pankevich Apr 28 '14 at 10:31
  • @Stanislaw well, I cannot either anymore. Just tested address stayed the same after calling `copy`. – coverback Apr 28 '14 at 10:31
  • @Stanislaw (starting a new test project) ... please wait ... ;) – CouchDeveloper Apr 28 '14 at 10:33
  • 1
    @coverback: NO test can prove that blocks do not start out on the stack. Your test results are fully consistent with the hypothesis that the block that starts out on the stack, and then is copied by ARC prior to assigning to the variable. ARC is allowed to insert extra copies for blocks arbitrarily. – newacct Apr 29 '14 at 06:11
  • @newacct yes, you're 100% right. That's why answer says "this is not an answer". Now that there's Matt's actual _answer_, I hope it gets voted up and mine will stay as mere test without any proof. – coverback Apr 29 '14 at 06:17
  • @StanislavPankevich this is because block doesn't capture anything and doesn't do anything so it's promoted to '__NSGlobalBlock__' or something like that, so it won't be created on stack nor heap. I believe compiler will simply create this pure function somewhere in executable and __NSGlobalBlock__ class will wrap around it. So calling 'copy' won't do anything to it, because there is no need to move it to heap as it doesn't operate on or capture any data. – Yurii Romanchenko Aug 17 '18 at 17:59