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