巩鹏军的博客

主页

Block引起的循环引用案例剖析(三)ARC下的__block关键字

12 Mar 2014

为了避免Block相关的循环引用,MRC下使用__block关键字,ARC下使用__Weak关键字,它们有什么区别?既然ARC下的__weak关键字更好,那为什么在ARC下看到有时使用__block呢?是为了避免循环引用吗?不是的话,那ARC下的__block关键字又是干什么用的呢?下面我们就来一一回答这几个问题,并举一个真实的案例说明ARC下的__weak和__block两个关键字各自的妙用。


问题一:为避免循环引用,MRC下使用__block关键字,ARC下使用__Weak关键字,它们效果一样吗?有什么区别?

参见本系列的前两篇文章

我们知道

在避免循环引用这一点上,两者效果相同,都可以避免block引用self,从而打破block和self之间的循环引用。但当被引用对象self释放时,两者的行为就不一样了:在MRC下,指定了__block关键字的变量指向的地址不变,这时,如果访问该变量就会产生访问错误令程序崩溃;而在ARC下的__weak关键字变量会自动被置为nil,这时,如果访问该变量,nil,像Objective-C一贯一样,不会有任何副作用。所以ARC下的__weak可以让程序更安全更健壮。

问题二:既然ARC下的__weak关键字更好,那为什么在ARC下看到有时使用__block呢?是为了避免循环引用吗?那ARC下的__block关键字又是干什么用的呢?

__block关键字的本意是表示通过引用捕获变量,通过引用(即变量的地址)来访问变量,这样就可以给该变量赋值(writable)。即在block里面可以给指定了__block关键字的外部变量赋值,它是为了让block将一些信息传递到block之外。比如下面的例子:

__block int x = 1; //  x lives in block storage
void (^printXAndY)(int) = ^(int y) {
    x = x + y;
    printf("%d %d\n", x, y);
};
printXAndY(2); // prints: 3 2
// x is now 3

在该例子中,x被指定了__block关键字,这样printXAndY这个block里面的代码就可以将新的值赋给block外部的变量x:x = x + y;

同样,对于id类型的变量,也可以指定__block关键字,这样,也可以在block里面给这样的变量赋值,含义是让它指向新的对象(retain或者non-retain)。

所以,在ARC下,__block关键字不是为了解决循环引用的,而是为了解决让变量可以在block内部被赋值。比如我们在SDWebImage的库代码里可以看到如下的写法: SDWebImageDownloader.m (line:108)

- (id<SDWebImageOperation>)downloadImageWithURL:...
{
    __block SDWebImageDownloaderOperation *operation;
    __weak SDWebImageDownloader *wself = self;
    
    [self addProgressCallback: ... createCallback:^
     {
         // ...
         operation = [SDWebImageDownloaderOperation.alloc
                      // ...
                      ];
     }];
    
    return operation;
}

上面代码的含义是这样的:

  1. 代码中变量operation被指定了__block关键字:

     __block SDWebImageDownloaderOperation *operation; 
    
  2. 这是为了在block(‘createCallback’)里可以对外部变量operation赋值:

     operation = [SDWebImageDownloaderOperation.alloc
    
  3. 从而让block(‘createCallback’)外部的代码可以引用在block里面生成的对象:

     return operation;
    

如果不使用__block关键字,那么block(‘createCallback’)里就无法给外部变量operation赋值,运行时,return operation;就永远返回nil。其实,不用等到运行时,编译器在编译时就会发现这个问题,并报告错误。


结论

  1. 在ARC下,__weak关键字是为了解决循环引用。
  2. 在ARC下,__block关键字是为了让block里可以对外部的变量赋值(writable),从而将block里面计算的值或生成的对象传递出去。