使用强引用/弱引用self来打破retain周期

10

我读过一些关于使用/self来打破保留循环的文章,但我仍然不太清楚它们是如何工作的。我理解使用__weak typeof(self) weakSelf = self来创建对自身的弱引用,但我对强引用感到困惑。据我所知,强引用是为了让self在块结束前有一个强引用,以免其被释放。那么,为什么需要__strong typeof(self) strongSelf = weakSelf呢?这难道不会指向自身对象吗?那么为什么不直接使用strongSelf = self 呢?

5个回答

13

模式是:

__weak typeof(self) weakSelf = self;

[manager someAsynchronousMethodWithCompletionHandler:^{
    typeof(self) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

这个思想是完成处理程序块只会对self进行弱引用,所以如果在完成块被调用之前释放了self,那么它将安全地被释放,因为该块对其没有强引用。(一个常见的例子是当视图控制器启动一些异步网络请求来更新视图时,如果在网络请求完成之前关闭了视图控制器,则没必要保留已经不存在的视图控制器实例。)

但是,这种weakSelf/strongSelf模式还可以确保相反的情况发生,即完成块已经开始并且在self释放之前遇到strongSelf行,该块将确保self在运行该块的过程中被保留(即使该块在不同的线程上运行,也不能在完成块的运行中途被释放)。这有许多潜在的好处(从对象完整性到消除竞态条件)。有时你实际上并不需要"weakSelf/strongSelf舞蹈"的 strongSelf 部分,但在需要时它是一个非常宝贵的工具。

然而,如果在块内部有一行代码说 typeof(self) strongSelf = self (而不是weakSelf),那么在该语句的右侧出现self就会导致块在前面对self进行强引用,完全破坏了使用weakSelf的目的。


3
我认为你的模式不正确。strongSelf从self的内部分配应该从weakself进行。 - Jeff Laing
在那个代码块里应该写成 typeof(self) strongSelf = weakSelf; - JaredH
дҪҶжҳҜ strongSelf = weakSelf дёҚдјҡдҪҝ strongSelf жҢҮеҗ‘ self еҗ—пјҹжҲ–иҖ…еҸ‘з”ҹдәҶзЁҚеҫ®дёҚеҗҢзҡ„дәӢжғ…еҗ—пјҹ - somtingwong
1
如果 self 还没有被释放,strongSelf = weakSelf 会将 strongSelf 设置为 self。但是如果在代码执行到这一行时 self 已经被释放了,那么 weakSelf 将会是 nil,因此 strongSelf 也会被设置为 nil - Rob
所以如果 self 没有被释放,那么 strongSelf 将会被设置为 self,这不就等同于 strongSelf = self 吗?这难道不会导致一个保留循环,使得 strongSelf 永远不会随着 self 一起被释放吗? - somtingwong
1
@user1813076 - 不,这不一样,这就是为什么你没有强引用循环的原因。如果一个块直接引用 self,那么该块在实例化时会建立对 self 的强引用(这很容易导致强引用循环)。但是,如果您引用 weakSelf,则该块的存在不会建立对 self 的强引用。如果您有一行代码说 strongSelf = weakSelf,那么当块被实例化时,它不会建立强引用,而只有在运行该行代码时才会建立强引用。因此没有强引用循环。 - Rob

13

在块内引用的任何非弱对象都会导致对该对象进行隐式保留,因为块正在创建时发生,而不是执行时发生。

如果您直接从self初始化inner strongSelf,则会保留self的值,并可能导致保留循环。

另一方面,如果您从weakSelf初始化它,则不会保留weakSelf的值。

这就是两步的原因。外部代码将self的值复制到weakSelf中,但ARC不添加保留,因为它是__weak()。

块“创建”将weakSelf的值复制(或至少在执行时间使其值可用)。您看不到它将其复制到哪里,但确实这样做了。

在块“执行”时间,块将“weakSelf的值”(如果此时已释放self,则为nil)复制到strongSelf中,然后ARC应用保留。因此,在块的持续时间内,如果strongSelf引用的对象一开始仍然存在,则该对象将保持活动状态。如果仅依赖于weakSelf,则在块执行期间的任何时候都可能变为nil。

请注意,弱/强模式是多余的 - 许多示例实际上依赖于weakSelf将变为nil,而块将静默地成为无操作的集合(发送消息到nil)。

保留循环通常仅在(a)将块的引用保存在self.property中或(b)将块移交给其他对象(通知管理器等),并告诉该其他对象在您dealloc时忘记它时发生。在这两种情况下,只要块存在,您的dealloc就永远不会被调用。

当人们说“完成此操作的方法是使用弱/强模式”时,他们假设最坏的情况。


哦,所以weakSelf = selfweakSelf赋值为self的一个副本?这似乎有点奇怪,我以为它指向原始的self。那是因为块还是变量赋值的工作方式? - somtingwong
@user1813076 - 不,weakSelf = self并不会将weakSelf赋值给self的副本。weakSelf是指向self的指针,当self被释放时,该指针被设置为nil - Rob
这并不受闭包和变量作用域的隐秘性的帮助。实际上被复制的是变量"weakSelf"本身,而不是它所指向的对象。 - Jeff Laing
考虑到weakSelf、strongSelf和self都是指向单个对象的变量。当把该指针复制到另一个变量时,ARC会介入并“增加引用计数”的被引用对象,除非变量为__weak(),此时它将安排该变量在所引用的对象释放后为空。块秘密地复制它们所引用的所有外部作用域变量,不是对象,只是变量。(注意:这忽略了__block,这是一个完全不同的事情) - Jeff Laing
在使用NotificationCenter调用块中的实例时,为什么我们需要使用_weak?这不会创建任何保留循环,对吧? - Ayyappa
这个问题很难回答,因为苹果公司引入了两个不同的东西,都叫做“通知中心”,这让人感到不太方便。如果你在谈论NSNotificationCenter,那么文档上说你的block是被复制的,而不是被保留的。复制品将在你使用的任何self/weakself变量上具有相同的保留策略。所以,是的,我相信通知块足够强大,可以使你的对象保持活动状态,除非你进行weakself/self洗牌。 - Jeff Laing

1

有正确的答案,但我真的不知道它们是否是你问题的答案。它们通常解释了使用self的块中的保留周期问题,但你的问题是:

那为什么不只是strongSelf = self

这个问题的答案是:

如果这样做,self将成为块闭包的一部分,并始终被保留。弱引用就没有意义了。


0

简单来说

在 block 前我们使用 __weak typeof(self) weakSelf = self - 这只是指向 self 的指针,不会引起循环引用

只有当 block 执行时才会进行 retain 操作

--> 如果我们将其设为 strong,则会启动循环引用并消耗内存,即使我们不调用 block


0

必须要明确一点,你不是在使用弱/强引用模式来避免保留循环!无论你使用 strong 引用自己还是 weakself,都不能避免保留循环。保留循环是通过引用变量作用域来打破的!当它到达作用域的末尾时,引用变量将被解除分配。弱/强引用模式仅仅是一种保护机制,防止您引用 nil 做出强引用,因为在创建和运行块之前,self 可能已经被释放。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接