理解 weak-strong dance

问题

大家都知道使用block的时候可以通过__weak关键字来避免循环引用,如下:

1
2
3
4
5

protocol Navigatable {
var navigator: Navigator! { get set }
}

1
2
3
4
__weak typeof(self) weakSelf = self;
self.handler = ^{
NSLog(@"Self is %@", weakSelf);
};

这时handler持有了block对象,捕获了weakSelf,延长了这个局部变量的生命周期,其实block内外两个weakSelf的变量值虽然一样,但变量地址不同,如浅拷贝,对self的引用都是弱引用,当前self被释放(如pop出当前控制器),weakSelf就指向了nil。

先说说block的情况:

手动调用 Block 的实例方法copy
Block 作为函数返回值返回
将 Block 赋值给附有__strong修饰符的成员变量
在方法名中含有usingBlock的 Cocoa 框架方法或 GCD 的 API 中传递 Block

以上四种情况,如果 Block 在栈上,那就复制一份到堆上,如果 Block 已经在堆上,那就把该 Block 的引用计数加1。

也就是说如果将以上的block放到gcd中运行耗时或延时操作过程中,self被释放了,block还被引用着,如果这是讲self(nil)作为参数传入,就会导致crash。

解决

1
2
3
4
5
6
7
8
9
10
11
NSLog(@"block执行中,Self is %@", weakSelf);
NSTimeInterval interval = 3.0;
self.handler = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"block执行中,Self is %@", weakSelf);
NSLog(@"block执行中,weakSelfcount is %@", [weakSelf valueForKey:@"retainCount"]);
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"当前控制器退出后,Self is %@", strongSelf);
});
};
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(interval * NSEC_PER_SEC)), dispatch_get_main_queue(), weakSelf.handler);

通过在 block 里对 weakSelf 进行强引用,间接对 self 进行强引用(网上资料说 self 的引用计数会加1),而当 block 执行完毕,strongSelf 被释放,self 的引用计数会减1。这样就可以既避免循环引用,也防止了 self 引用过程中被提前释放。

这样写还是有风险,在 block 之前,self 就被释放了,weakSelf 为 nil,那么strongSelf 的变量值也是 nil,还是会crash。strongSelf 只能保证 self 被使用过程中不会被提前释放,所以要使用之前还是要判断 strongSelf 是否为 nil。