保留循环发生在两个对象存储彼此的强引用时。最简单的情况是对象a 存储对对象b 的强引用,而b 则相反[1]。保留周期在 Objective-C 中是一个问题,因为它们使 ARC 相信这些对象总是在使用中,即使这些对象没有从其他任何地方引用。
让我们回顾一些例子。您有对象z,它分配a 和b,使用它们,然后处理它们。如果a 和b 首先在它们之间创建了一个保留循环,a 和b 将不会被释放。如果您多次这样做,您将严重泄漏内存。
保留循环的另一个真实世界示例是,如果 a 分配并强引用 b 对象,但您还存储了从 b 到 a 的强引用(对象图中的许多较小的对象可能需要访问他们的父母)。
在这些情况下,最常用的解决方案是确保包含的对象仅对其包含对象具有弱引用,并确保兄弟对象之间不包含对彼此的强引用。
另一种解决方案(通常不太优雅,但在某些情况下可能合适)可能是在a 中使用某种自定义cleanup 方法,它消除了对b 的引用。因此,b 将在调用 cleanup 时被释放(如果 b 在其他地方没有被强烈引用)。这很麻烦,因为您不能从a 的dealloc 执行此操作(如果存在保留周期,它永远不会被调用)并且因为您必须记住在适当的时间调用cleanup。
- 请注意,保留周期也是可传递的(例如,对象
a 强引用 b,而 c 强引用 a)。
话虽如此:块的内存管理很难理解。
您的第一个示例可以创建一个 临时 保留周期(并且仅当您的 self 对象存储对 someObject 的强引用时)。当块完成执行并被释放时,这个临时保留周期就会消失。
在执行期间,self 将存储对someObject 的引用,将someObject 存储到block,并将block 再次存储到self。但同样,它只是暂时的,因为该块不会永久存储在任何地方(除非[someObject successBlock:failure:] 实现这样做,但这对于完成块并不常见)。
因此,在您的第一个示例中,保留周期不是问题。
通常,只有在某个对象正在存储块而不是直接执行它时,块内的保留循环才会成为问题。那么很容易看出self强引用了block,而block强引用了self。请注意,从块内访问任何 ivar 会自动生成对该块中 self 的强引用。
确保包含的对象不会强引用其容器的等效方法是使用__weak SelfClass *weakSelf = self 来访问方法和 ivars(如果通过访问器访问 ivars 会更好,就像使用属性时一样)。您的块对self 的引用将是弱引用(它不是副本,它是弱引用),这将允许self 在不再强引用时解除分配参考。
可以说,始终在所有块内使用weakSelf 是一种很好的做法,无论是否存储,以防万一。我想知道为什么 Apple 没有将此作为默认行为。这样做通常不会对块代码造成任何损害,即使实际上是不需要的。
__block 很少用于指向对象的变量,因为 Objective-C 并没有强制这样的对象的不变性。
如果你有一个指向对象的指针,你可以调用它的方法,这些方法可以修改它,有或没有__block。 __block 对基本类型(int、float 等)的变量更有用(仅?)。请参阅 here 了解当您将 __block 与对象指针变量一起使用时会发生什么。您还可以在 Apple 的 Blocks Programming Topics 中阅读有关 __block 的更多信息。
编辑:修正了在对象指针上使用 __block 的错误。感谢@KevinDiTraglia 的指点。