定义Objective-C块作为属性 - 最佳实践

16

我最近看到了一个苹果文档,其中展示了一个块的以下属性声明:

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

此外,这篇文章指出:

注意:您应该将属性属性指定为copy,因为需要复制一个块来跟踪其捕获状态以在原始范围之外进行访问。当使用自动引用计数时,您无需担心此问题,因为它会自动发生,但属性属性最佳实践是显示所得到的行为。有关更多信息,请参见块编程主题。

我还阅读了建议的块编程主题,但没有找到相关的内容。

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

谢谢


对于初学者来说,如果你想知道如何使用代码块,可以参考以下链接:https://dev59.com/Jm865IYBdhLWcg3wM7u0#20760583 - Fattie
3个回答

25

默认情况下,块在堆栈上创建。这意味着它们仅存在于它们被创建的作用域中。

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

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


如果你捕获了self并且创建了一个保留循环,它们也不应该被声明为弱引用吗? - cfischer
绝对不行,因为大多数情况下该属性将是区块的唯一引用,因此如果该属性是弱引用,该区块将立即消失。 - gnasher729
嗨,Joris。为了绝对清楚,如果你简单地省略复制,你是否实际上是在说它会被复制?再次强调,如果你完全省略复制,与有复制的情况相比,实际上结果、处理和执行是否完全相同?干杯。 - Fattie

7

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

要将块从堆栈移动到堆(并因此给予它正常的 Objective-C 内存管理语义和延长生命周期),必须通过 [theBlock copy]Block_copy(theBlock) 等复制块。一旦位于堆上,可以按需要管理块的生命周期,通过保留/释放它来实现。(是的,在 ARC 中也适用,只是您不必自己调用 -retain/-release。)

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


然而,上面的两个答案似乎暗示这必须要做,并且无论如何都会被ARC自动完成,显式地包含复制的唯一原因是为了风格上的考虑——“提醒每个人”实际上正在发生什么。你有什么想法? - Fattie

6
你所提到的“最佳实践”只是说:“由于ARC会无论你在这里写什么都会神奇地复制你的block,所以最好你明确地写‘copy’,以免混淆将来查看你的代码的人。”
解释如下:
通常情况下,你不需要复制(或保留)一个block。只有当你期望该block在声明它的范围被销毁后继续使用时,才需要复制它。复制会将block移动到堆上。
-块编程主题:使用块、复制块
显然,将一个block分配给属性意味着它可能在声明它的范围被销毁后继续使用。因此,根据块编程主题,应该使用Block_copy将该block复制到堆上。
但是ARC会为你处理这个问题。

在ARC模式下,当您将块传递到堆栈中,例如在返回值中,块会自动工作。您不再需要调用Block Copy。
转换到ARC

请注意,这与块的保留语义无关。没有办法让块的上下文存在而不将其移动到堆栈(即将弹出)并移动到堆上。因此,无论您使用什么属性修饰符来修饰您的@property,ARC仍然会复制该块。


如果有人担心效率或功能问题,那么一旦一个块被复制,结果就是不可变的,进一步尝试复制只会保留它,就像其他不可变实例一样。 - gnasher729
嗨jenmore,感谢您的帖子。正如我之前问过的那样,您认为...“为了绝对清楚,实际上,如果您完全省略复制,与有复制相比,结果、处理和执行是否完全相同?”我们怎么看?干杯! - Fattie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接