使用__block和__weak

27

我已经阅读了这篇帖子:什么是“__block”关键字?,讨论了__block的用途,但有一个回答让我感到困惑。它说__block用于避免保留循环,但在它下面的评论中让我不确定。

我像这样使用它:

 self.someProperty = x; //where x is some object (id)
 __block __weak VP_User *this = self;

 //begin a callback-style block
     this.someProperty = nil;

我需要同时使用__block__weak吗?这种方式有什么明显的问题吗?


实际上,其中一个答案(已获赞,但未被采纳)说:“__block有时用于避免保留循环”。 - Adam Jenkins
我不明白那个;使用__weak是避免这种情况的方法。 - trojanfoe
我也不清楚 - 因此在这个帖子中询问澄清。使用两者都可以吗? - Adam Jenkins
我认为是这样的;它们是不同的事情,而且保留循环的问题通常出现在块内部的代码中,而不是在块外部。 - trojanfoe
4
或许这种混淆是因为在“手动引用计数”中,__block说明符确实会导致对象被保留,从而可以避免保留循环。但这种行为在ARC中是不同的(这在“转换到ARC发布说明”中的某处有记录)。 - Martin R
显示剩余2条评论
2个回答

61

__block 是一个存储修饰符。它指定变量应直接被块捕获,而不是复制它。如果您需要修改原始变量,则此功能非常有用,如以下示例所示。

__block NSString *aString = @"Hey!"; 
void(^aBlock)() = ^{ aString = @"Hello!" }; // without __block you couldn't modify aString
NSLog(@"%@", aString); // Hey!
aBlock();
NSLog(@"%@", aString); // Hello!

在ARC中,这会导致变量被自动保留,以便可以在块实现中安全地引用。在上面的例子中,当在块上下文中捕获时,aString将收到一个retain消息。
请注意,在MRC(手动引用计数)中不是这样的,在其中,变量被引用而没有被保留。
将其标记为__weak会导致变量不被保留,因此块直接引用它但不保留它。这可能很危险,因为如果块的生命周期比变量长,那么它将引用垃圾内存(并有可能崩溃)。
以下是clang doc中相关的段落:

在Objective-C和Objective-C++语言中,我们允许对对象类型的__block变量使用__weak限定符。[...] 此限定符使这些变量保持不被发送保留消息。这会故意导致悬空指针,如果块(或副本)的生命周期超过此对象的生命周期。

最后,声称__block可以用来避免强引用循环(也称为保留循环)在ARC上下文中是完全错误的。由于在ARC中__block会导致变量被强引用,实际上更可能导致它们发生。例如,在MRC中,此代码可以打破保留循环。
__block typeof(self) blockSelf = self; //this would retain self in ARC!
[self methodThatTakesABlock:^ {
    [blockSelf doSomething];
}];

要在ARC中实现相同的结果,通常需要执行以下操作:

__weak typeof(self) weakSelf = self;
[self methodThatTakesABlock:^ {
    [weakSelf doSomething];
}];

所以,如果我理解正确的话,在ARC中同时使用__block和__weak是没有意义的,因为它们实际上是相反的东西? - Adam Jenkins
3
不行。如果您想修改块内的对象,则持有对它的引用会导致保留循环。我想不出一个好的例子,但可能存在合法的用途。这就是为什么虽然有潜在风险,但文档中允许它的原因。 - Gabriele Petronella
在 ARC 中,这会导致变量被自动保留。保留变量的方法是通过增加其引用计数来实现。需要保持可访问性的“变量”是指针 *aString,而不是对象本身。如何保留一个指针呢?同样是通过增加其引用计数。 - Hot Licks
如果一个块被移动到堆上,__block 变量也会被透明地移动。 - Gabriele Petronella
2
如果我没错的话,要发生保留循环,你必须在 self 中保留对块的直接或间接引用(例如 self.block = block)。因此,仅仅使用块内部的 self 并不会创建保留循环。如果你不存储该块,则不需要 weakSelf - Ferran Maylinch
显示剩余5条评论

14

如果您想在块内更改变量的值,应该使用 __block

例如:

__block BOOL result = NO;
dispatch_sync(dispatch_get_main_queue(), ^{
  ...
  result = YES;
  ...
});
你应该使用__weak来避免保留环。
例如:
__weak typeof(self) wself = self;
self.foobarCompletion = ^{
  ...
  wself.foo = YES;
  ...
};

如果需要的话,您可以将它们组合在一起。


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