巩鹏军的博客

主页

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

04 Mar 2014

在Objective-C中,类的某个成员是block类型,容易引起循环引用,造成内存泄露。本文举例分析在使用自动引用计数(ARC)情况下的代码片段和解决方案。


错误代码

// ARC-Enabled (-fobjc-arc)
typedef void (^blk_t)(void);
@interface MyObject : NSObject {
    blk_t blk_;
}
@end
@implementation MyObject
- (id)init {
    self = [super init];
    blk_ = ^{NSLog(@"self = %@", self);}; 
    return self;
}
- (void)dealloc {
    NSLog(@"dealloc"); 
}
@end

int main() {
    id obj = [[MyObject alloc] init]; 
    NSLog(@"%@", obj);
    return 0;
}

错误现象

-[MyObject dealloc]永远不会被调用到。

错误原理

self强引用block,block强引用self,循环引用,谁都无法释放,表现为self的dealloc无法被调用。

  1. 在ARC环境下,成员变量声明blk_t blk_;等价于__strong blk_t blk_;
  2. 在-[MyObject init]中,当那个block literal赋给blk_时,该block被从栈上复制到堆上,self持有堆上block的引用。
  3. 在ARC环境下,block代码内部直接引用self,也是__strong引用。
  4. 在那个block literal被从栈上复制到堆上时,self的引用计数增加,堆上block持有self的引用。
  5. 因为self和堆上的block相互强引用,谁都无法释放,导致内存泄露。

解决方案一:简单方案

- (id)init {
    self = [super init];
    __weak typeof(self) wself = self;
    blk_ = ^{NSLog(@"self = %@", wself);}; 
    return self;
}

解决方案二:完善方案

上面的解决方案一存在两个问题:

因此,可以在block代码开始的时候,将wself赋值给一个__strong引用的临时变量来解决上面的两个问题。

- (id)init {
    self = [super init];
    __weak typeof(self) wself = self;
    blk_ = ^{
        __strong typeof(self) sself = wself;
        if(!sself)
            return;
        NSLog(@"self = %@", sself);
    }; 
    return self;
}

block内部的sself是临时变量,只会在block被执行的短暂时间段持有对self的引用,在block代码执行完成之后(出了sself的作用域),该sself会自动被释放。 这样虽然在block被执行的短暂时间段存在循环引用,但是该循环引用是动态的、短暂的、必定被打破的,所以是安全的,不会引起内存泄露。

真实案例

最后,举一个使用方案二的实例,AFNetworking的实现代码: UIImageView+AFNetworking