基于本文回答

播面 播面

刷题像听歌,多听自然懂
0
评论

为什么在 ARC 下,Block 属性建议使用 copy(或者 strong)修饰?

知识点图片

在 ARC(Automatic Reference Counting)环境下,Block 属性建议使用 copy 修饰,但使用 strong 也是完全可以的。

要理解其中的原因,我们需要从 Block 的内存位置变化以及 ARC 的工作原理两个方面来解释。

核心原因:从“栈”移动到“堆”

Block 在内存中主要有三种形态:

  1. _NSConcreteGlobalBlock (全局块):不捕获任何外部变量,存在于数据区(.data),类似全局变量,生命周期伴随整个 App。
  2. _NSConcreteStackBlock (栈块):捕获了外部变量,默认创建在上。
  3. _NSConcreteMallocBlock (堆块):当栈块被 copy 后,会复制到上。

1. MRC 时代的遗留习惯 (The Origin)

在 MRC(手动管理内存)时代,Block 创建时默认是在上的。

  • 如果一个 Block 在栈上,当定义它的函数或方法返回时,栈帧被销毁,Block 的内存也会被回收。
  • 如果此时你只是用 retain(相当于 ARC 的 strong)指向它,你持有的只是一个指向已销毁栈内存的野指针。当你稍后调用这个 Block 时,程序就会崩溃。
  • 解决方案:必须对 Block 发送 copy 消息,将其从栈复制到上,这样它才能拥有独立的生命周期,在作用域外存活。

因此,在 MRC 时代,Block 属性必须使用 copy 修饰。

2. ARC 的自动优化 (The Modern Way)

到了 ARC 时代,编译器做了大量优化。当 Block 被赋值给一个 strong 类型的对象(包括属性)时,编译器会自动帮你调用 copy 方法。

这意味着:

plaintext
// 在 ARC 下,这两者在底层行为上几乎是一样的
@property (nonatomic, copy) void (^myBlock)(void);
@property (nonatomic, strong) void (^myBlock)(void);

当你执行 self.myBlock = ^{ ... }; 时,无论修饰符是 copy 还是 strong,编译器都会确保这个 Block 被复制到了堆上。

为什么现在依然建议使用 copy

虽然 strong 在 ARC 下也能正常工作,但业界规范和 Apple 官方文档依然推荐使用 copy,主要基于以下几点:

  1. 语义明确(Semantics)
    Block 的行为与普通的 Objective-C 对象(如 UIView)不同。普通对象通常是引用传递,而 Block 经常代表一段代码逻辑的“快照”或“闭包”。使用 copy 显式地表达了行为意图:“我需要一份独立的、在堆上的拷贝,不受原栈帧销毁的影响。” 这符合 Block 必须移至堆上才能安全使用的底层逻辑。

  2. 遵循传统与习惯
    由于 MRC 时代的强制要求,使用 copy 修饰 Block 已经成为了 Objective-C 开发者的肌肉记忆和标准惯例。保持这种写法可以让代码风格统一,减少阅读者的困惑。

  3. 防止可变性问题(类似 NSString)
    虽然 Block 不像 NSString 那样有 NSMutableString 子类,但 copy 属性的一般原则是“赋值时生成不可变副本”。对于 Block 来说,copy 操作不仅是内存位置的移动(栈->堆),也是所有权的确立。

总结

  • 本质原因:Block 默认在栈上,为了在作用域外使用,必须 copy 到堆上。
  • ARC 现状:ARC 会自动处理 Block 的 copy,所以 strongcopy 效果一样,都不会 Crash。
  • 最佳实践建议继续使用 copy。这是一种“显式编程”,清晰地表达了将 Block 移至堆上的意图,也符合 Objective-C 的编码规范。

代码示例:

plaintext
// 推荐写法
@property (nonatomic, copy) void (^completionHandler)(BOOL success);

// 可行写法(ARC下安全,但不那么“地道”)
@property (nonatomic, strong) void (^completionHandler)(BOOL success);
00:00
00:00