16

我最近遇到了一份Apple 文档,其中显示了以下块的属性声明:

@interface XYZObject : NSObject
@property (copy) void (^blockProperty)(void);
@end

此外,这篇文章指出:

注意:您应该将 copy 指定为 property 属性,因为需要复制块以跟踪其在原始范围之外捕获的状态。这不是您在使用自动引用计数时需要担心的事情,因为它会自动发生,但最好让 property 属性显示结果行为。有关更多信息,请参阅块编程主题。

我还阅读了建议的Blocks Programming Topics,但没有找到任何相关的内容。

我仍然很好奇为什么将块属性定义为“副本”是最佳实践。如果您有一个好的答案,请尝试区分 ARC 和 MRC 差异(如果有)。

谢谢

4

3 回答 3

25

默认情况下,块是在堆栈上创建的。这意味着它们只存在于它们被创建的范围内。

如果您想稍后访问它们,则必须通过向copy块对象发送消息来将它们复制到堆中。一旦 ARC 检测到一个块需要在其创建的范围之外访问,它就会为您执行此操作。作为最佳实践,您将任何块属性声明为副本,因为这是它应该在自动内存管理下的方式。

阅读Mike Ash 的Objective-C 中的堆栈和堆对象,了解有关堆栈与堆的更多信息。

于 2013-02-01T16:10:15.753 回答
7

默认情况下,块在堆栈上分配。这是一种优化,因为堆栈分配比堆分配便宜得多。堆栈分配意味着,默认情况下,当声明它的范围退出时,块将不再存在。因此,具有retain语义的块属性将导致指向不再存在的块的悬空指针。

要将块从堆栈移动到堆(从而赋予其正常的 Objective-C 内存管理语义和延长的生命周期),您必须通过[theBlock copy]Block_copy(theBlock)等复制块。一旦在堆上,块的生命周期可以管理为需要保留/释放它。(是的,这也适用于 ARC,您不必自己打电话。-retain-release

因此,您希望使用copy语义声明块属性,以便在设置属性时复制块,避免指向基于堆栈的块的悬空指针。

于 2013-02-01T16:11:54.490 回答
6

您所指的“最佳实践”只是简单地说,“因为无论您在这里写什么,ARC 都会神奇地复制您的块,最好您明确地写出‘复制’,以免让后代在查看您的代码时感到困惑。”

解释如下:

通常,您不需要复制(或保留)块。仅当您希望在声明它的范围内销毁块后使用该块时,您才需要制作一份副本。复制将块移动到堆中。
–Blocks 编程主题:使用 Blocks,复制 Blocks

显然,将块分配给属性意味着它可以在声明它的范围被破坏后使用。因此,根据Blocks Programming Topics应该将块复制到堆中Block_copy

但是 ARC 会为您解决这个问题:

当您在 ARC 模式下将块向上传递到堆栈时,块“正常工作”,例如在返回中。您不必再调用 Block Copy。
–<a href="https://developer.apple.com/library/ios/#releasenotes/ObjectiveC/RN-TransitioningToARC/Introduction/Introduction.html" rel="noreferrer">过渡到 ARC

请注意,这与块的语义无关。retain如果不将块的上下文移出(即将弹出的)堆栈并移至堆,则根本无法存在块的上下文。所以不管你有什么属性@property,ARC仍然会复制这个块。

于 2013-02-01T16:27:37.793 回答