10

我正在使用 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);
    }
}

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

但是,在优化级别“最快、最小”的情况下,它仍然是NSStackBlock.

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

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

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

编辑:为了澄清 callbackBlock 是如何被声明的——就在我的@implementation 上面这些方法是这样的:

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

@end
4

2 回答 2

14

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

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

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

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

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

当您从函数或方法返回块时,ARC 会自动复制块。在这种情况下,编译器知道它必须为您复制到堆中,因此它会这样做。

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

我希望这会有所帮助。

于 2012-08-21T20:06:50.480 回答
4

这是对 Firoze 答案的长评论。

文档自动引用计数”第 7.5 节指出:

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

这是我所看到的行为。

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

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

于 2012-08-21T21:13:07.560 回答