块中的强引用,它会被保留吗?

4
我从公司文档中发现了这个代码片段:
__weak __typeof(self)weakSelf = self;
dispatch_async(dispatch_get_main_queue(),
^{
      __strong __typeof(weakSelf)strongSelf = weakSelf; 
      // Do stuff
});

可以保留吗?

这是使用块避免保留循环的正确方法。强引用指向弱引用,因此可以避免保留循环。我通常使用__strong typeof(self)strongSelf = weakSelf;,因为在两种情况下都是相同的self - Dennis W.
但是,我认为块外的弱引用已经足够了。这样可以吗? - Quang Dam
是的,你说得对。或者在代码块之外引用 __weak CLASSNAME *weakSelf = self; 并在代码块中使用 weakSelf 变量。 - Sunil Sharma
苹果的块编程指南只展示了使用weakself,然而在2012年WWDC视频《使用块、GCD和XPC的异步设计模式》中,他们使用strongself作为一种检查self是否为空的手段,在执行块中的代码之前。 - Dennis W.
3个回答

10
有两个原因需要在块内捕获 weak 引用。
  1. 避免保留循环

  2. 创建无操作的情况。

前者已经被反复讨论过了。后者更有趣。 例子 所讨论的块是图像下载的完成处理程序。当下载完成时,它将显示在图像视图中。
如果图像视图已经被释放(例如用户切换到新视图),则不需要执行任何操作。由于图像视图没有对该块的引用,因此不存在保留循环的危险。但是,捕获一个 weak 引用可以使图像视图在块执行之前被释放。因此,如果用户在图像下载之前切换视图,则块最终什么也不做,因为它的 weak 引用已经被设置为 nil。如果图像视图在块执行过程中部分释放,那也没关系,因为它只会将图像视图上的操作变成无操作,而不是将整个块变成无操作。
然而,有时候块希望出现无操作行为,但仅当引用在开始时已经是 nil(或达到代码路径的某个特定点)时才这样做。如果在块执行时,对象仍然存在,则必须完全执行该块。如果它在其他线程上被释放,则无法在中途停止。 例子 完成块的目的是向图像添加一个字符串定义的标题。如果字符串已经被释放,则不会添加标题。但是,如果在后处理开始时字符串仍然存在,则必须保持其活动状态,以避免尝试使用 nil 引用创建属性字符串,因为这会导致崩溃。
在这种情况下,应该使用 weak 引用来捕获字符串,以便可以由其他线程释放它(从而不添加标题)。但是,在块内使用字符串之前,必须将其 strong 地捕获,以避免创建属性字符串时出现崩溃。

谢谢你的好文章,但仅从英文来看,我无法确定你的意思。你能添加与叙述相符的代码吗?此外,这两个示例在我的看法中似乎可以直接编写代码,而不需要添加任何有趣的强引用未保留的堆栈变量。 - danh
2
嘿 - 这个问题一直困扰着我,所以我问了一个在苹果iOS上工作的朋友。简短的答案是,你是对的。我们使用weakSelf来避免循环引用,并使用strong copy来防止块执行期间的释放。然而,我们需要这种方法的清单相当晦涩:(a)与块的保留周期(罕见)(b)多线程使用self时不确定是否释放(罕见)(c)块中提到self指针超过一次的代码(d)使用self指针会有害的情况下,nil-ed(非常罕见)。abc*d = 罕见。(继续...) - danh
1
罕见,但你是正确的。你在这里努力举例说明的是最后一个(d)条件,我认为你并没有完全成功。尽管如此,感谢你教给我新东西。+1,在这里。我会保留我的答案,因为它并没有错。我想删除我的错误评论,但也许我会添加一个最后的评论,以防其他人发现我的无知和新的理解有用。 - danh

1
如果您使用这个:

__weak __typeof(self)weakSelf = self;
dispatch_async(dispatch_get_main_queue(),
^{ 
    // Do stuff
});

当 block 执行时,self 可能会被释放。因此,我们最好将 weakSelf 转换为 strongSelf,以确保 self 在 block 完成执行之前一直留在内存中。

在使用 __strong __typeof(weakSelf)strongSelf = weakSelf 后,self 将引用本地的栈变量。因此,它不会被保留。


1

在块外部的弱引用已经足够了。

__weak __typeof(self)weakSelf = self;
dispatch_async(dispatch_get_main_queue(),
^{
      // silliness: __strong __typeof(weakSelf)strongSelf = weakSelf; 
      // Do stuff with weakSelf here
});

事实上,这也是可以的:

dispatch_async(dispatch_get_main_queue(),
^{
    // self self self, go ahead and mention self here
});

请勿将该代码块复制到其他地方,并在该代码块中提及其他地方。
@property (nonatomic, strong) void (^myBlock)(void);

self.myBlock = ^void(void) {
    // don't mention self here!
};

这里的想法是块会保留它们提到的对象,我们试图避免循环引用,所以object -> block -> another_object是可以的,但是object -> block -> another_object -> 任意数量的间接引用 -> object就是循环引用,这是不好的。
循环引用很糟糕,因为如果一个对象在其他地方被保留,那么它就无法被释放,因此它所保留的东西也无法被释放。如果两个东西相互保留,那么它们都被卡住了,无法被释放,因为每个东西都被某些东西保留。 编辑直到今天我误解的是弱变量的强制复制并不总是愚蠢的。它可能是相关的,但是适用的情况非常有限。

我也见过几次,这是因为有人不理解复制了另一个不理解的人的代码所致。 - danh
2
在块内使用弱引用的强版本有其原因。这不是误解的结果,尽管那些不理解的人可能会过度使用它。在块内创建一个强引用可以确保所引用的对象在块执行期间保持活动状态。如果没有这个保证,该对象可能会在执行过程中被释放。有时这是可以接受的,有时则不是。 - Avi
1
@BryanChen - 是的,当weakSelf或其他东西保留了该块时,这是必需的。如果不存在该循环,则不需要weakSelf。块内的“strong”声明是多余的。如果您想要它强大,请一开始就不要声明它为弱。 - danh
如果使用了weakSelf,则需要包含strongSelf块。在这个问题中,weakSelf没有意义,因此不需要有strongSelf。但是,在大多数需要打破保留循环的情况下,需要使用weakSelfstrongSelf来确保self不会在块执行过程中被释放。 - Bryan Chen
@Avi - 我觉得我可能没有理解到重点。有没有某个地方可以参考,解释一下你试图向我解释的内容呢? - danh
显示剩余6条评论

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