In C, correctness cannot be inferred from running any number of tests, because you could be seeing undefined behavior. To properly know what is correct, you need to consult the language specification. In this case, the ARC specification.
It is instructive to first review when it is necessary to copy a block under MRC. Basically, a block that captures variables can start out on the stack. What this means is when you see a block literal, the compiler can replace it with a hidden local variable in that scope that contains the object structure itself, by value. Since local variables are only valid in the scope they are declared in, that is why blocks from block literals are only valid in the scope the literal is in, unless it is copied.
Furthermore, there is the additional rule that, if a function takes a parameter of block pointer type, it makes no assumptions about whether it's a stack block or not. It is only guaranteed that the block is valid at the time the block is called. However, this pretty much means that the block is valid for the entire duration of the function call, because 1) if it is a stack block, and it is valid when the function was called, that means somewhere up the stack where the block was created, the call is still within the scope of the stack literal; therefore it will still be in scope by the end of the function call; 2) if it is a heap block or global block, it is subject to the same memory management rules as other objects.
From this, we can deduce where it is necessary to copy. Let's consider some cases:
- If the block from a block literal is returned from the function: It needs to be copied, since the block escapes from the scope of the literal
- If the block from a block literal is stored in an instance variable: It needs to be copied, since the block escapes from the scope of the literal
- If the block is captured by another block: It does not need to be copied, since the capturing block, if copied, will retain all captured variables of object type AND copy all captured variables of block type. Thus, the only situation where our block would escape this scope would be if the block that captures it escapes the scope; but in order to do that, that block must be copied, which in turn copies our block.
- If the block from a block literal is passed to another function, and that function's parameter is of block pointer type: It does not need to be copied, since the function does not assume that it was copied. This means that any function that takes a block and needs to "store it for later" must take responsibility for copying the block. And indeed this is the case (e.g.
dispatch_async
).
- If the block from a block literal is passed to another function, and that function's parameter is not of block pointer type (e.g.
-addObject:
): It needs to be copied if you know that this function stores it for later. The reason it needs to be copied is that the function cannot take responsibility for copying the block, since it does not know it is taking a block.
So if your code in the question was in MRC, -whatever
would not need to copy anything. -whenSettledDo:
will need to copy the block, since it is passed to addObject:
, a method that takes a generic object, type id
, and doesn't know it's taking a block.
Now, let's look at which of these copies ARC takes care for you. Section 7.5 says
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.
What the first part means is that, in most places where you assign to a strong reference of block pointer type (which normally causes a retain for object pointer types), it will be copied. However, there are some exceptions: 1) In the beginning of the first sentence, it says that a parameter of block pointer type is not guaranteed to be copied; 2) In the second sentence, it says that if a block is only used as an argument to a call, it is not guaranteed to be copied.
What does this mean for the code in your question? handlerFixed
is a strong reference of block pointer type, and the result is used in two places, more than just an argument to a call, thus assigning to it assigns a copy. If however, you had passed a block literal directly to addObject:
, then there is not guaranteed to be a copy (since it's used only as an argument to a call), and you would need to copy it explicitly (as we discussed that the block passed to addObject:
needs to be copied).
When you used settledHandler
directly, since settledHandler
is a parameter, it is not automatically copied, so when you pass it to addObject:
, you need to copy it explicitly, because as we discussed that the block passed to addObject:
needs to be copied.
So in conclusion, in ARC you need to explicitly copy when passing a block to a function that doesn't specifically take block arguments (like addObject:
), if it's a block literal, or it's a parameter variable that you're passing.