在这个块中强引用 'self' 很可能会导致循环引用问题。

21

我有一个带有block的请求。但是编译器会发出警告:

"在这个block中强引用了'self',很可能导致循环引用"

__weak typeof(self) weakSelf = self;
[generalInstaImage setImageWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:data[@"images"][@"low_resolution"][@"url"]]] placeholderImage:[UIImage imageNamed:@"Default"] success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
    NSLog(@"success");
    [generalInstaImage setImage: image];
    [weakSelf saveImage:generalInstaImage.image withName:data[@"id"]];

    } failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
        NSLog(@"fail");
}];

我尝试像这样编写例子:weakSelf.generalInstaImage,但编译器生成了一个错误,无法编译。


什么是错误?尝试使用“id”或实际类名而不是“typeof(self)”。 - Fonix
2个回答

63
请注意以下警告:
在该块中强引用“self”可能导致保留循环。
当您收到上述警告时,应检查块是否包含以下内容:
任何对“self”的明确引用;或者
由于引用了任何实例变量而导致的对“self”的隐式引用。
我们假设有一个简单的类属性,它是一个块(这将与你的问题一样产生“保留循环”警告,但会使我的例子更简单)。
@property (nonatomic, copy) void (^block)(void);

假设我们想在代码块内使用另一个类属性:
@property (nonatomic, strong) NSString *someString;

如果您在块内引用了self(在我的下面的示例中,访问此属性时),您显然会收到有关保留周期风险的警告:
self.block = ^{
    NSLog(@"%@", self.someString);
};

那可以通过您建议的模式来解决,即:
__weak typeof(self) weakSelf = self;

self.block = ^{
    NSLog(@"%@", weakSelf.someString);
};

不太明显的是,如果你在块内引用类的实例变量,也会收到“保留循环”警告,例如:

self.block = ^{
    NSLog(@"%@", _someString);
};

这是因为_someString实例变量隐含地引用了self,实际上等同于:

self.block = ^{
    NSLog(@"%@", self->_someString);
};

您可能倾向于在此处尝试采用弱引用模式,但是您不能这样做。如果您尝试使用weakSelf->_someString语法模式,编译器将会警告您:

由于竞争条件可能导致空值,请勿解引用__weak指针,先将其分配给strong变量

因此,您需要使用weakSelf模式来解决此问题,但同时在块内部创建一个本地的strong变量,并使用它来解引用实例变量。
__weak typeof(self) weakSelf = self;

self.block = ^{
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        NSLog(@"%@", strongSelf->_someString);

        // or better, just use the property
        //
        // NSLog(@"%@", strongSelf.someString);
    }
};

作为旁注,这个在块中创建本地strong引用的方法,即strongSelf,还有其他优点,尤其是如果完成块在不同的线程上异步运行,则您不必担心在块执行时self被释放而导致意外后果。 weakSelf/strongSelf模式在处理块属性并希望防止保留循环(也称为强引用循环)的情况下非常有用,同时确保在执行完成块的过程中无法释放self
值得一提的是,在“转换到ARC发布说明”中的使用生命周期限定符避免强引用循环部分中,Apple进一步讨论了这种模式。
你报告说在你的示例中引用 weakSelf.generalInstaImage 时收到了一些“错误”。这是解决“保留周期”警告的正确方法,因此如果你收到了某些警告,请与我们分享,并向我们展示你如何声明该属性。

3
非常完整的回答!+1 - Lorenzo B
不,它不会生成异常。它将导致未定义的行为(可能是分段错误)。 - newacct
@newacct 我的主要观点是打消使用弱指针解引用实例变量的想法,但我感谢您的澄清。 - Rob
关于内存管理和块的解释很好,但问题中并不清楚该块是generalInstaImage的属性。有时候XCode无法智能地检测到这些情况,而这就是其中之一,因为setImageWithURLRequest:requestWithURL:placeholderImage:success:是来自SDWebImage的方法,似乎没有声明任何块属性。 - Pablo A.
Pablo,是的,编译器不知道另一个类是否正确避免了强引用循环。它只知道传递 self 可能会导致这样的循环。(也许他们使用“可能”有些过头了。)话虽如此,我们经常进行防御性编程,假设任何异步方法都可能保留对块的引用。理想情况下,我们希望我们的类尽可能松散耦合,不应该对其他类的行为做出太多假设。在我看来,无论如何,你应该使用 weakSelf 模式。 - Rob

2

使用__unsafe_unretained typeof(self) weakSelf = self

意思是创建一个弱引用的weakSelf,用于避免循环引用。


3
如果在调用块之前self被释放,那么这可能会消除编译器警告,但是会产生灾难性的结果,因为你将拥有指向已释放对象的悬空指针。显然,这非常不安全。 - Rob

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