【问题标题】:ARC __block and __weakARC __block 和 __weak
【发布时间】:2014-09-19 20:47:16
【问题描述】:

假设我正在尝试从一个块内访问self

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

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

让我感到困惑的是使用/不使用 __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 关键字。我是不是想错了?

【问题讨论】:

    标签: ios objective-c automatic-ref-counting


    【解决方案1】:

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

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

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

    如果指向的对象被释放,所有弱引用(包括被 块)被Objective-C运行时设置为nil

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

    【讨论】:

    • 假设 MyCLass 实现了一个真正的副本......因为-copyWithZone: 可以保留......这是完全合法的,并且几乎可以在任何不可变对象中完成。
    • @GradyPlayer:也许我表达得很糟糕,但我的意思是该块在其块上下文中保存了一个强(或弱)指针与@的当前内容 987654328@(或me)。不涉及copy 方法
    • 是的,当有人对他们做某事时,有时 SO 会循环回到顶部...有时我必须在几个月或几年后有一个书呆子挑剔...但是可以复制对象阻止捕获,所以我认为这是不正确的......
    • @GradyPlayer:你不认为这是不正确的吗?或者你认为这是不对的?
    • 我认为捕获对象可以但不一定会导致块中的对象具有新地址。
    【解决方案2】:

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

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

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

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

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

    1. 请注意,保留周期也是可传递的(例如,对象 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 的指点。

    【讨论】:

    • 不错的答案,但你确定最后的陈述吗?我正在研究使用 __block 而不是 __weak 作为引用类型的问题,它们具有不同的行为,__weak 引用变为 nil 而 __block 引用没有。我认为它更接近于对象引用的强指针。
    • 感谢您的评论,您是对的。我修正了那一点答案。
    • 不确定总是弱引用 self 是否正确。有时我认为您可能希望该块保留引用,因此它不会让它被释放。据我了解,只有在使用强引用会导致保留周期时才应使用它。
    【解决方案3】:

    您的第一个示例不会创建一个从不结束的保留循环。会有保留循环,好吧,但是一旦块完成,块对someObject 的引用将被删除。所以someObject 至少会存活到块完成。 这种临时保留周期可能是好事也可能是坏事,这取决于您想要什么:

    如果您需要您的 someObject 至少在它的块完成之前还活着,没关系。但是,如果没有理由保留该对象,则应使用“弱”引用来实现它。

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

    【讨论】:

    • 如果块完成后,对 them 的引用被删除,这不是更准确吗?
    • 这确实是正确的。 +1,因为它解释了为什么它不创建永久保留周期。许多新程序员总是在使用weakSelf,因为他们在接受的答案中列出的保留周期上被误导了。虽然这对于大多数应用程序来说都很好,但更复杂的应用程序会看到在块执行完成之前释放引用的问题,如果您稍后尝试引用这些对象,则会导致崩溃。我想你的意思是在最后一句中说weakSelf 可能为零。
    【解决方案4】:

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

    你错了'someObject and self never get de-alloced':当块被释放时 self 将被释放。块将被 someObject 释放。 SomeObject 将在没有更多引用时被释放。所以如果你的 self-object 拥有 someObject,当你不再需要 someObject 时就释放它。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多