I'm using Xcode 4.3.3 and developing for iOS 5.0+. In development of an ARC iOS application, I've started using blocks as a callback mechanism for asynchronous operations. The app works fine in the simulator and on the device.
Then I ran it the profiler for the first time, and it started crashing on me nearly right away - in particular, an EXC_BAD_ACCESS when trying to invoke the first callback block.
After a little investigation, it was clear the difference in behavior was because the profiler runs in "Release mode" by default - in particular, with Optimization Level set to "Fastest, Smallest [-Os]" instead of "None [-O0]".
For example, the following code (simplified for this question) would crash when trying to execute the callbackBlock:
- (void) setCallbackBlock:(void (^)(NSString *input))block
{
callbackBlock = block;
}
- (void) invokeCallbackWithInput:(NSString *)input
{
if (callbackBlock) {
callbackBlock(input);
}
}
Debugging into it, calling setCallbackBlock with optimization level set to "None", the incoming block would be an NSStackBlock
, and the callbackBlock would become an NSMallocBlock
.
However, with Optimization Level "Fastest, Smallest", it remained an NSStackBlock
.
Changing the setter code to use [block copy]
fixes the crashing problem (based on iOS 5 blocks crash only with Release Build).
However, another related question indicates that this shouldn't be necessary with ARC - block variables are copied to the heap in ARC - Why does Objective-C block still work without copying it to the heap?
So my question: What's going on here, and why? (Also, how can both of those answers be correct...?)
Edit: To clarify how callbackBlock is being declared - just above my @implementation where those methods are is this:
@interface MyClass ()
{
void (^callbackBlock)(NSString *input);
}
@end
So my question: What's going on here, and why? (Also, how can both of those answers be correct...?)
I actually think the answer to the other question is wrong, in that it doesn't answer that particular question about blocks in ARC. The question is about passing a stack based block from one function/method to another. The answer is about something different, which is capturing __block variables within a block. That's a different issue.
The answer to your question is in the FAQ of the Transitioning to ARC Release Notes:
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.
So the way this works is that when you pass a block (in your case a block literal allocated on the stack), the compiler does not copy that block when it initializes the parameter for that call. The called function or method has the responsibility to copy that block itself if needed.
Where ARC does copy blocks automatically is when you are returning a block from a function or method. In that case, the compiler knows that it must do a copy to the heap for you, and so it does.
So your setter should be doing a block copy, even with ARC.
I hope that helps.
This is a long comment on Firoze's answer.
The document Automatic Reference Counting" section 7.5 states:
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 aBlock_copy
. The optimizer may remove such copies when it sees that the result is used only as an argument to a call.
And this is the behavior I have seen.
So if callbackBlock
is a strong instance variable then ARC should copy any stack-allocated block pass in block
. I.e. the Debug version was correct and the Release version is a compiler bug.
If this is correct then you've found a compiler bug and should report it. Reporting it would not be bad either way, it should produce a definitive answer.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With