在 C 中,无法通过运行任意数量的测试来推断正确性,因为您可能会看到未定义的行为。要正确知道什么是正确的,您需要查阅语言规范。在这种情况下,ARC 规范.
当需要在 MRC 下复制一个块时,首先审查是有益的。基本上,捕获变量的块可以从堆栈开始。这意味着当您看到块文字时,编译器可以将其替换为该范围内包含对象结构本身的隐藏局部变量,按值。由于局部变量仅在它们声明的范围内有效,这就是为什么来自块文字的块仅在文字所在的范围内有效,除非它被复制。
此外,还有一个附加规则,如果函数接受块指针类型的参数,它不会假设它是否是堆栈块。仅保证该块在调用该块时有效。然而,这几乎意味着该块在函数调用的整个持续时间内都是有效的,因为 1) 如果它是一个堆栈块,并且在调用函数时它是有效的,这意味着该块所在的堆栈上的某个位置创建时,调用仍在堆栈字面量范围内;因此在函数调用结束时它仍然在作用域内;2)如果是堆块或全局块,则与其他对象一样受制于相同的内存管理规则。
由此,我们可以推断出哪里需要复制。让我们考虑一些情况:
- 如果从函数返回块文字中的块: 它需要被复制,因为块从文字的范围逃逸
- 如果块文字中的块存储在实例变量中:它需要被复制,因为块从文字的范围中逃脱
- 如果块被另一个块捕获:它不需要被复制,因为如果被复制,捕获块将保留所有捕获的对象类型的变量并复制所有捕获的块类型的变量。因此,我们的块会逃离这个范围的唯一情况是,如果捕获它的块逃离了这个范围;但为了做到这一点,必须复制该块,这反过来又复制了我们的块。
- 如果块文字中的块被传递给另一个函数,并且该函数的参数是块指针类型:它不需要被复制,因为该函数不假定它被复制。这意味着任何需要一个块并需要“存储它以备后用”的函数都必须负责复制该块。确实是这样(例如
dispatch_async
)。
- 如果块文字中的块被传递给另一个函数,并且该函数的参数不是块指针类型(例如
-addObject:
):如果您知道该函数将其存储以供以后使用,则需要复制它。它需要被复制的原因是函数不能负责复制块,因为它不知道它正在获取一个块。
因此,如果您在问题中的代码在 MRC 中,则-whatever
不需要复制任何内容。-whenSettledDo:
将需要复制块,因为它被传递给addObject:
一个方法,该方法采用通用对象 type id
,并且不知道它正在占用一个块。
现在,让我们看看这些副本中的哪些 ARC 会照顾您。第 7.5 节说
除了作为初始化 __strong 参数变量或读取 __weak 变量的一部分完成的保留之外,每当这些语义要求保留块指针类型的值时,它都具有 Block_copy 的效果。当优化器发现结果仅用作调用的参数时,它可能会删除此类副本。
第一部分的意思是,在您分配给块指针类型的强引用的大多数地方(这通常会导致对象指针类型的保留),它将被复制。但是,也有一些例外: 1)在第一句的开头,它说不保证块指针类型的参数被复制;2)在第二句中,它说如果一个块仅用作调用的参数,则不能保证被复制。
这对您问题中的代码意味着什么?handlerFixed
是块指针类型的强引用,结果在两个地方使用,不仅仅是一个调用的参数,因此分配给它分配一个副本。但是,如果您已将块文字直接传递给addObject:
,则不能保证是副本(因为它仅用作调用的参数),并且您需要显式复制它(正如我们讨论的块传递toaddObject:
需要复制)。
直接使用时settledHandler
,由于settledHandler
是参数,不会自动复制,所以在传递给 时addObject:
,需要显式复制,因为正如我们所讨论的,传递给的块addObject:
需要被复制。
因此,总而言之,在 ARC 中,您需要在将块传递给不专门采用块参数(如addObject:
)的函数时显式复制,如果它是块文字,或者它是您传递的参数变量。