块可能会导致保留循环。

3
我已经为 NSOperationBlock 编写了以下类别。
@implementation NSOperationQueue (Extensions)

-(void)addAsynchronousOperationWithBlock:(void (^)(block))operationBlock
{
    dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);

    block signal = ^ {
        dispatch_semaphore_signal(semaphore);
    };

    [self addOperationWithBlock:^{
        operationBlock(signal);
        dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
        dispatch_release(semaphore);
    }];
}

@end

看起来代码运行正常,但是当我调用它时(如下面的代码片段所示),我收到了一个警告:

该块可能会导致保留循环

[_queue addAsynchronousOperationWithBlock:^(block signal) {        [self foo:nil];
         signal();
}];

foo 是使用该类别(category)的方法。

使用NSOperationQueue中的addOperationWithBlock:代码不会显示警告:

[_queue addOperationWithBlock:^ {        [self foo:nil];
}];

我真的不理解它。 特别是我不理解的是: 在这两种情况下,我是否应该实际使用弱引用?如果我不使用弱引用,那么这两个片段是否会导致保留循环?


更正:静态分析器会警告方法名称以“set”或“add”开头,但不会针对“addOperationWithBlock”。 - Martin R
我猜你最后的代码示例中是指“addOperationWithBlock”,而不是“addAsynchronousOperationWithBlock”? - Martin R
我修改了问题以使其更清晰。 - lucaconlaq
队列 == self,因此self保留block,block保留self == _queue ERGO一个循环。 - Daij-Djan
3个回答

9

当你在一个块内使用self时,它会被该块捕获,并且可能导致保留循环。当self(或其强引用的某个内容)持有对块的强引用时,就会出现保留循环。为了避免这种潜在的情况,声明一个弱指针并在块中使用:

YourClassName * __weak weakSelf = self;

[_queue addAsynchronousOperationWithBlock:^(block signal) {
    [weakSelf foo:nil];
}];

2
具体来说,如果该块是由自己拥有的,即使是间接的拥有方式,就会有问题。否则就没事了。 - Catfish_Man
很好的观点,我更新了答案以使其更明确。 - jszumski
好的,但是为什么我使用addOperationWithBlock没有同样的问题呢?这里块的强引用在哪里? - lucaconlaq
1
但是这两个代码片段都*捕获了self。分析器在一个案例中抱怨而在另一个案例中不抱怨的原因是一些方法名被硬编码到分析器中,详见我上面对问题的评论。 - Martin R
1
请注意,捕获self并不是一件坏事 - 它可以用来保持对象在块被调用之前一直存在,参见https://dev59.com/ZGfWa4cB1Zd3GeqPlu72#12220731。 - Martin R
谢谢回答,有一些我还是不明白,我想编辑问题可能是表达我的疑问更合适的方式。 - lucaconlaq

4
jszumski的回答本质上是正确的,但重要的是要正确进行“弱引用”操作。在他的代码基础上,正确的形式应该是:

YourClassName * __weak weakSelf = self;

[_queue addAsynchronousOperationWithBlock:^(block signal) {
    YourClassName * strongSelf = weakSelf;
    if (strongSelf)
         [weakSelf foo:nil];
}];

因此,我们通过一个强引用来捕获weakSelf。如果不这样做,当你正在使用weakSelf时(因为你对它的引用是弱引用),weakSelf可能已经不存在了。
请参阅我的书中关于防止由块引起的潜在保留循环的其他方法: http://www.apeth.com/iOSBook/ch12.html#_unusual_memory_management_situations

2
我通常也会使用这种模式,但是如果你只使用[weakSelf foo:nil];,实际上对象将无法被释放,详见https://dev59.com/32Up5IYBdhLWcg3wdXWa#15267291:在块中访问`weakSelf`通过`objc_loadWeak()`完成,这会保留并自动释放对象。 - Martin R
感谢您的答案,也感谢您提供的链接。阅读它让我学到了很多知识。虽然我对自己的问题仍有些疑问,但我已经编辑了问题以表达它们。 - lucaconlaq

4
为了简化其他人在此处所写的内容:
1. 两个代码示例都不会创建长时间维持的保留循环,这种循环会浪费内存。
2. Xcode抱怨你的addAsynchronousOperationWithBlock方法是因为它有一个可疑的名称。它不会抱怨addOperationWithBlock,因为它知道addOperationWithBlock的特殊性质,可以覆盖它的怀疑。
3. 要消除警告,请使用__weak(请参阅jszumski和matt的答案)或将addAsynchronousOperationWithBlock重命名为不以“add”或“set”开头的名称。
稍微解释一下这些内容:
1. 如果self拥有_queue,则会出现短暂的保留循环。self将拥有_queue,_queue将拥有块,并且调用[self foo:]的块将拥有self。但是,一旦块完成运行,_queue将释放它们,并且循环将被打破。
2. 静态分析器已被编程成对以“set”和“add”开头的方法名称表示怀疑。这些名称表明该方法可能永久地保留传递的块,可能会创建永久的保留循环。因此警告您的方法。它不会抱怨-[NSOperationQueue addOperationWithBlock:],因为它被告知在运行块后释放它们的人知道。
3. 如果使用__weak,则分析器不会抱怨,因为不存在引用循环的可能。如果重命名方法,则分析器不会抱怨,因为没有理由怀疑传递给它的块会被永久地保留。

感谢您的好解释。现在我明白了。 - lucaconlaq

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