__block 与 __weak

下面举个使用__block的例子:

__block BOOL found = NO;
NSSet *aSet = [NSSet setWithObjects: @"Alpha", @"Beta",     @"Gamma", @"X", nil];
NSString *string = @"gamma";
[aSet enumerateObjectsUsingBlock:^(id obj, BOOL *stop) {
 if ([obj localizedCaseInsensitiveCompare:string] == NSOrderedSame) {
     *stop = YES;
     found = YES;
 }
}];  
// 在这之后, found == YES

上面例子的意思是,迭代一个NSSet,当迭代出的一个字符处的小写编码和string一致,则停止搜索,将found值修改为YES,注意,found在block之外被创建,并被__block修改,且在block内发生修改。

那同样的这一段代码,我们将__block修改为__weak会怎么样呢,我们尝试一下: image

我们发现这里报错了, 警告的信息是:

'__weak' only applies to Objective-C object or block pointer types; type here is 'BOOL' (aka 'bool')

这里告诉了我们很重要的一条,__weak只可以用于oc对象或者block的指针类型,并不能用于类似bool,int这种的数值类型,而上面的__block明显可以。

Variable is not assignable (missing __block type specifier)

这里的意思是,这个found变量是assignable,并不可在block内部修改,主要的原因是它是在外部创建的,block内对于外部变量并不能直接修改,而使用__block则可以在block内部修改外部变量。

我们发现,如果Xcode报一个循环引用的错误,我们只需要用__block去修饰,Xcode就不报错了,而使用__weak修饰则在block内部修改外部变量时,依然报错,那是否我们可以把所有的__weak多可以换成__block呢?似乎__weak没有什么用处,因为__block修饰的东西,不仅可以在block里面访问,并且还能被修改,那是否__weak就没有用,或者__block是万能的呢?

我们在clang的官方文档中找到如下的内容:

In the Objective-C and Objective-C++ languages, we allow the _weak specifier for _block variables of object type. If garbage collection is not enabled, this qualifier causes these variables to be kept without retain messages being sent. This knowingly leads to dangling pointers if the Block (or a copy) outlives the lifetime of this object.

我们发现,在ARC的情况下,__block可以用来解决循环引用问题是错的,实际上,在ARC情况下,oc对象一般是自动retain的,__block实际上又给此对象添加了一个引用,下面的代码在ARC的情况下,可能会造成循环引用:

__block typeof(self) blockSelf = self; 
[self methodThatTakesABlock:^ {
   [blockSelf doSomething];
}];

而使用__weak的话,则可避免这个问题:

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

总结(ARC情况下):

  • __block和__weak一般都是用于修饰某个外部变量,而此外部变量需要在block内部使用或被修改
  • __weak修饰的变量(oc对象或指针)在block内部使用时,并不进行retain,或者说,block并不持有此变量的引用,所以不会造成循环引用的问题,同时,__weak修饰的变量在block内部并不能被修改
  • __block修饰的变量在block内部使用时,会进行retain操作,可能造成循环引用问题,__block修饰的变量可在block内部被修改
  • 有需要可以结合二者一起使用

Article by 付军