8

假设我试图self从一个块内访问:

[someObject successBlock:^(NSArray *result) {
    [self someSuccessMethod];
} failure:^(NSString *errorMessage, int status) {
    [self someFailureMethod];
}];

我知道这会创建一个保留周期,someObject并且self永远不会被取消分配。

令我困惑的是使用/不使用__block关键字实际发生的情况。我可以通过__weak引用 self 来修复保留周期:

__weak MyClass* me = self;
[someObject successBlock:^(NSArray *result) {
    [me someSuccessMethod];
} failure:^(NSString *errorMessage, int status) {
    [me someFailureMethod];
}];

我不需要在__block这里使用,因为我不想me从块内进行修改。据我了解,如果我不使用,则会在块内引用__block副本。me我的问题是:如果块内引用的只是对象的副本,为什么原始代码块会创建保留循环?我猜对的引用self只是一个副本,因为我从不使用__block关键字。我想错了吗?

4

4 回答 4

7

在第一种情况下,块捕获self,即将 的副本保存self为另一个指针。这会增加指向对象的保留计数,并导致保留循环。

在第二种情况下,该块捕获me,即将 的副本保存me为另一个 指针。这不会增加保留计数,因此不会导致保留周期。

(如果你打印块外部和内部的地址me你会看到地址是不同的。块有自己的指向对象的弱指针。)

如果指向的对象被释放,所有的弱引用(包括被块保存的)都由nilObjective-C 运行时设置。

(我只是希望我做对了。)

于 2013-07-23T15:15:11.000 回答
4

当两个对象相互存储强引用时,就会发生保留循环。最简单的情况是对象a存储对对象的强引用bb执行相反的操作 [1]。保留周期是 Objective-C 中的一个问题,因为它们使 ARC 相信这些对象总是在使用中,即使这些对象没有从其他任何地方引用。

让我们回顾一些例子。你有一个对象z,它分配aand b,使用它们,然后处理它们。如果a并且b首先在它们之间创建了一个保留循环,a并且b不会被释放。如果您多次这样做,您将严重泄漏内存。

保留循环的另一个真实示例是如果a分配并强引用一个b对象,但您还存储了一个强引用 from bto a(对象图中的许多较小的对象可能需要访问它们的 parent)。

在这些情况下,最常见的解决方案是确保包含的对象仅对其包含对象具有弱引用,并确保兄弟对象之间不包含对彼此的强引用。

另一种解决方案(通常不太优雅,但在某些情况下可能合适)可能是使用某种自定义cleanup方法,a因为它不引用b. 因此在被调用b时会被释放cleanup(如果b在其他地方没有强烈引用)。这很麻烦,因为您不能从a's执行此操作dealloc(如果存在保留周期,它永远不会被调用)并且因为您必须记住cleanup在适当的时间调用。

  1. 请注意,保留循环也是可传递的(例如,对象a强烈引用b哪个强烈引用c哪个强烈引用a)。

尽管如此:块的内存管理很难理解。

您的第一个示例可以创建一个临时保留周期(并且仅当您的self对象存储对 的强引用时someObject)。当块完成执行并被释放时,这个临时保留周期就会消失。

在执行期间,将再次存储对、和 的self引用。但同样,它只是暂时的,因为该块不会永久存储在任何地方(除非实现这样做,但这对于完成块并不常见)。someObjectsomeObjectblockblockself[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 等)的变量更有用(仅?)。请参阅此处__block了解与对象指针变量一起使用时会发生什么。您还可以__block在Apple的Blocks Programming Topics中阅读更多信息。

编辑:修正了关于__block对象指针使用的错误。感谢@KevinDiTraglia 指出它。

于 2013-07-23T16:04:07.350 回答
3

您的第一个示例不会创建一个永无止境的保留周期。会有保留循环,好吧,但是一旦块完成,块对的引用someObject将被删除。因此someObject,至少在块完成之前将存在。这种临时保留周期可能是好事也可能是坏事,这取决于您想要什么:

如果你someObject至少需要你的生命直到它的块完成,那没关系。但是,如果没有理由保留该对象,则应使用“弱”引用来实现它。

例如。myObject 是一个视图控制器,它在这些块中从网上获取图片。如果你someObject从导航控制器弹出那个,控制器在获取图片后将无法显示它,所以没有必要保留它。成功或错误无关紧要,用户不再对someObject应该获取的图片感兴趣。在这种情况下,使用weak 是更好的选择,但是块中的代码应该比selfnil 更好。

于 2014-02-16T15:29:27.467 回答
0

您可以将 self 作为块的参数进行路径,准确地给出变量名称“self”,这将防止在块中自我保留。

你错了'someObject and self never get de-alloced':当块被释放时,self将被释放。块将被 someObject 释放。SomeObject 将在没有更多引用时被释放。因此,如果您的自身对象拥有 someObject,则在不再需要时释放 someObject。

于 2013-07-23T15:34:15.853 回答