解释SDWebImage代码中__weak和__strong使用原因

5
我认为我很好地理解了强引用和弱引用关键字,但是我不理解下面的代码如何使用。这段代码来自Olivier Poitrey在github上提供的SDWebImage。我理解强引用和弱引用的方式如此描述:iOS5中强引用和弱引用存储的解释
下面的代码以一种让我感到好奇的方式使用__weak和__strong关键字。它不是我习惯看到的子父关系或委托模式中使用的弱引用。然而,我确定这是一个经常使用的模式,因为我在其他代码中也看到过它。它在另一个线程上运行的块之前设置了一个__weak引用。然后,在块内部,将弱引用设置为强引用。
我确信这是优秀且优雅的代码,所以我正在努力理解它。如果“self”在块运行之前停止存在,则弱self引用将归零。当块运行时,强引用也将被设置为零。因此,它将知道杀死其余的操作,因为self不再存在。我的理解正确吗?
现在,如果我们不使用__weak和__strong关键字会发生什么?如果我们只是在块内部检查self == nil会发生什么?由于块复制整个树,因此“self”永远不会为nil吗?
能否有人帮助揭开这段神奇的代码的秘密?能否有人验证或驳斥我的假设?
- (void)setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletedBlock)completedBlock;
{
    [self cancelCurrentImageLoad];

    self.image = placeholder;

    if (url)
    {
        __weak UIImageView *wself = self;
        id<SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
        {
            __strong UIImageView *sself = wself;
            if (!sself) return;
            if (image)
            {
                sself.image = image;
                [sself setNeedsLayout];
            }
            if (completedBlock && finished)
            {
                completedBlock(image, error, cacheType);
            }
        }];
        objc_setAssociatedObject(self, &operationKey, operation, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
}
2个回答

7
downloadWithUrl:方法可能需要很长时间,此时用户可能决定导航离开,因此不再需要SDWebImage对象。为了促进对象的早期清理,外部的self引用是弱引用。这样,downloadWithUrl方法不会阻止SDWebImage被释放
当然,如果您确实想要使用self,则需要一个强引用。因此,downloadWithUrl的完成块会抓住self的强引用。如果对象在此期间消失,则sself将为nil。否则,它将是一个有效的强引用,表示SDWebImage对象仍然存在,对象将在此时完成其工作。

另一个非常棒的答案。谢谢nneonneo。 - Ed Chin
1
这不仅仅是“促进对象的早期清理”。它可以在任何时候促进对象的清理 - 否则就会出现保留循环 - self 通过关联引用保留了 operationoperation 保留了该块。如果该块保留了 self,那么就会形成一个循环。 - newacct

3
我确定这是优秀而又优雅的代码,所以我试着去理解它。如果“self”在块运行之前消失了,弱引用将会被清零。当块运行时,强引用也会被清零。因此,它将知道杀掉其余的操作,因为self不再存在了。我的理解正确吗?
不,你想得太多了。__weak存储限定符就是这样:一个限定符。__weak对象显式地未被保留,但如果从一个强变量赋值,则它们不会自动设置为nil。实际上,那样做会破坏弱变量的目的!
现在,如果我们没有使用__weak和__strong关键字会发生什么?如果我们只是在块内检查self == nil会发生什么?"self"永远不会是nil,因为块复制了整个树吗?
检查实际上是不必要的,因为运行时将消息解析为nil到nil(尽管这可能对实现稍后很重要,谁能说呢)。在这一点上,你是正确的:如果没有那个小小的“弱转强”的舞蹈,那么self将被块保留,有可能创建一个相当恶心的保留循环。这就是我开始把所有这些联系起来的地方:
因为我们不希望块保留我们的变量,但我们也希望它在块的作用域内是强的,这样就不会发生任何奇怪的事情,所以self被赋值给一个弱指针。当块遇到我们的弱指针时,它不能保留它,所以self的引用计数保持不变,然后一旦进入块,我们立即回到一个强self变量,这样弱的变量就被释放了,我们就不必再担心它了。实际上,这意味着我们有一个可靠的保证,即self在整个块的执行过程中要么是一个值,要么是nil。很棒,是吧?

你的最后一段非常非常有帮助。谢谢CodaFi。 - Ed Chin
我不理解这句话的意思:"...我们回到一个强self变量,这样弱变量便会被释放"。sslef 是用来创建一个强引用,以确保当线程执行该块时,wself 不会被其他线程释放吗? - Boon
1
有点像。如果在块中不将其重新分配给强变量,则可能会在块执行过程中释放它并设置为nil。通过分配到强变量,您可以保证self在整个块执行期间要么是非nil的,要么是nil的。 - CodaFi

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