在块中捕获self (保留循环引用),并不总是必须的吗?

6
以下代码来自苹果提供的LazyTableImages示例代码(源代码在此处)。
在它们的完成块中,他们引用了self,这应该会导致保留循环……但我在Xcode中没有收到此警告,而在我的类似代码中,我会收到警告。
这是正确的吗?
也许我错过了这个细节。
- (void)startIconDownload:(AppRecord *)appRecord forIndexPath:(NSIndexPath *)indexPath
{
    IconDownloader *iconDownloader = [self.imageDownloadsInProgress objectForKey:indexPath];
    if (iconDownloader == nil) 
    {
        iconDownloader = [[IconDownloader alloc] init];
        iconDownloader.appRecord = appRecord;
        [iconDownloader setCompletionHandler:^{

            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

            // Display the newly loaded image
            cell.imageView.image = appRecord.appIcon;

            // Remove the IconDownloader from the in progress list.
            // This will result in it being deallocated.
            [self.imageDownloadsInProgress removeObjectForKey:indexPath];

        }];
        [self.imageDownloadsInProgress setObject:iconDownloader forKey:indexPath];
        [iconDownloader startDownload];  
    }
}

你是在说,@Abizern,没有警告是因为循环有多个级别吗? - jscs
没有警告是因为没有保留循环。 - Abizern
self 拥有 imageDownloadsInProgress,它拥有 iconDownloader,后者拥有其 completionHandler,该处理程序对 @Abizern 强引用了 self。这里存在一个保留循环。它可能不是一个问题,但确实存在。 - jscs
微妙的保留循环。但在我们这里的示例中,完成处理程序确实处理了取消iconDownloader对象的操作。根据代码更改,这是潜在的保留循环。 - Daniel
2
可能是Clang - Blocks retain cycle from naming convention?的重复问题 - 编译器使用命名约定来决定是否警告潜在的保留循环。 - Martin R
4个回答

4
你认为看到的保留循环是因为对象在字典中持有下载器。
确实,在块中存在对self的强引用,但只要完成处理程序始终运行,下载器将从字典中删除。最终,这个字典将为空,这意味着不会有任何对象持有自身,因此也没有保留循环。

2
编译器使用命名约定来决定是否警告潜在的保留循环,可以参考Clang - Blocks retain cycle from naming convention? - Martin R

0

selficonDownloader 没有强指针。它只在这个方法中被创建和作用域限定:

IconDownloader *iconDownloader = [self.imageDownloadsInProgress objectForKey:indexPath];

如果iconDownloader是一个强引用属性(self.iconDownloader),那么Xcode会检测到强引用循环。

iconDownloader 对象似乎是由 imageDownloadsInProgress 持有的,而 imageDownloadsInProgress 又被假定为由 self 持有;如果 iconDownloader 还负责持有其完成 Block,那么这就是一个强引用循环。 - jscs
很好的发现。我认为这里存在一个保留循环,但由于它经过了NSDictionary,Xcode没有创建警告。 - Aaron Brager
顺便提一下,如果您的数据经常更改,使用IconDownloader类是灾难的配方。如果存在任何可能导致数据在图像下载完成后位于不同索引路径的情况,您不应该使用indexPath来确定在哪里放置图像。 - Aaron Brager

0

没有警告是因为编译器尚未能够检测出所有可能的保留循环。

例如:

- (void)foo 
{
    _block = ^ { [self done]; }; // Warning: Possible retain cycle

    DSGenericBlock foo = ^ { [self done] }; 
    _block = foo;  // No warning.
}

如果您直接将该块分配给"self"的实例变量,您将收到"可能发生保留循环"的警告。相反,该块被分配给另一个对象,然后由self保留,因此编译器无法检测到循环(尽管循环确实存在)。

1
没有警告是因为没有保留循环。 - Abizern

0

捕获self本身并不会产生保留循环。这只是一个单一的引用。一个引用无法构建循环。通常的反模式是,在self的强属性中存储对块的额外引用。然后就有两个引用构成了循环。


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