块和ARC – 使用发布版本复制或崩溃(由优化级别引起)

我正在使用Xcode 4.3.3并开发iOS 5.0+。 在开发ARC iOS应用程序时,我开始使用块作为异步操作的回调机制。 该应用程序在模拟器和设备上正常工作。

然后我第一次运行它的探查器,它几乎立即开始崩溃 – 特别是,当尝试调用第一个回调块时,EXC_BAD_ACCESS。

经过一番调查后,很明显行为的差异是因为探测器默认以“发布模式”运行 – 特别是,优化级别设置为“最快,最小[-Os]”而不是“无[-O0] ”。

例如,以下代码(针对此问题进行了简化)在尝试执行callbackBlock时会崩溃:

- (void) setCallbackBlock:(void (^)(NSString *input))block { callbackBlock = block; } - (void) invokeCallbackWithInput:(NSString *)input { if (callbackBlock) { callbackBlock(input); } } 

调试它,调用优化级别设置为“None”的setCallbackBlock,传入的块将是NSStackBlock ,并且callbackBlock将成为NSMallocBlock

然而,在优化级别“最快,最小”的情况下,它仍然是一个NSStackBlock

更改setter代码以使用[block copy]修复崩溃问题(基于iOS 5块仅在Release Build中崩溃 )。

但是,另一个相关问题表明,ARC 不需要这样做 – 块变量被复制到ARC中的堆中 – 为什么Objective-C块仍然可以工作而不将其复制到堆中?

所以我的问题是:这里发生了什么,为什么? (另外,这两个答案如何才能正确……?)

编辑 :澄清如何声明callbackBlock – 就在我的@implementation之上,这些方法是这样的:

 @interface MyClass () { void (^callbackBlock)(NSString *input); } @end 

所以我的问题是:这里发生了什么,为什么? (另外,这两个答案如何才能正确……?)

我实际上认为另一个问题的答案是错误的,因为它没有回答关于ARC中的块的特定问题。 问题是将基于堆栈的块从一个函数/方法传递到另一个函数/方法。 答案是关于不同的东西,即在块中捕获__block变量。 这是一个不同的问题。

您的问题的答案在过渡到ARC发行说明的常见问题解答中:

在ARC模式下将块传递到堆栈时块“正常工作”,例如在返回中。 您不必再调用Block Copy。 将堆栈“向下”传递给arrayWithObjects:以及其他执行保留的方法时,仍需要使用[^ {} copy]。

因此,它的工作方式是,当您传递一个块(在您的情况下是在堆栈上分配的块文字)时,编译器在初始化该调用的参数时不会复制该块。 被调用的函数或方法有责任在需要时复制该块本身。

ARC从函数或方法返回块时自动复制块的位置。 在这种情况下,编译器知道它必须为您复制到堆,所以它确实如此。

因此,即使使用ARC,您的setter也应该进行块复制。

我希望有所帮助。

这是对Firoze答案的长篇评论。

文档自动引用计数“第7.5节规定:

除了作为初始化__strong参数变量或读取__weak变量的一部分完成的保留外,每当这些语义调用保留块指针类型的值时,它都具有Block_copy的效果。 当优化器看到结果仅用作调用的参数时,可以删除这些副本。

这就是我所看到的行为。

因此,如果callbackBlock是一个强实例变量,那么ARC应该在block 复制任何堆栈分配的块传递。 即Debug版本是正确的,Release版本是编译器错误。

如果这是正确的,那么你发现了一个编译器错误,应该报告它。 无论哪种方式报告它都不会坏,它应该产生明确的答案。