文章目录
  1. 1. 前言
  2. 2. 问题来源
  3. 3. Strong-Weak Dance
  4. 4. 思考
  5. 5. @strongify 和 @weakify
  6. 6. Swift 中的情况
  7. 7. 关于YYKit中block的细节
  8. 8. 参考

前言


在使用 Block 时,除了使用 __weak 修饰符避免循环引用外,还有一点经常容易忘记。苹果把它称为:“Strong-Weak Dance”。

问题来源


这是一种 强引用 –> 弱引用 –> 强引用 的变换过程。在弄明白为什么要如此大费周章之前,我们首先来看看一般的写法会有什么问题。

1
2
3
4
__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
[wself.property removeObserver: wself forKeyPath:@"pathName"];
};

这种写法可以避免循环引用,但是我们要考虑这样的问题:

假设 block 被放在子线程中执行,而且执行过程中 self 在主线程被释放了。由于 wself 是一个弱引用,因此会自动变为 nil。而在 KVO 中,这会导致崩溃。

Strong-Weak Dance


1
2
3
4
5
__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
__strong __typeof(wself) sself = wself; // 强引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
};

这样一来,self 所指向对象的引用计数变成 2,即使主线程中的 self 因为超出作用于而释放,对象的引用计数依然为 1,避免了对象的销毁。

思考


在和小伙伴的讨论过程中,他提出了几个问题。虽然都不难,但是有利于把各种知识融会贯通起来。

1.Q:下面这行代码,将一个弱引用的指针赋值给强引用的指针,可以起到强引用效果么?

1
__strong __typeof(wself) sself = wself;

A:会的。引用计数描述的是对象而不是指针。这句话的意思是:

sself 强引用 wself 指向的那个对象

因此对象的引用计数会增加一个。

2.Q:block 内部定义了sself,会不会因此强引用了 sself?
A:不会。block 只有截获外部变量时,才会引用它。如果是内部新建一个,则没有任何问题。
3.Q:如果在 block 内部没有强引用,而是通过 if 判断,是不是也可以,比如这样写:

1
2
3
4
5
6
 __weak MyViewController *wself = self;
wself.completionHandler = ^(NSInteger result) {
if (wself) { // 只有当 wself 不为 nil 时,才执行以下代码
[wself.property removeObserver: wself forKeyPath:@"pathName"];
}
};

A:不可以!考虑到多线程执行,也许在判断的时候,self 还没释放,但是执行 self 里面的代码时,就刚好释放了。

4.Q:那按照这个说法,block 内部强引用也没用啊。也许 block 执行以前,self 就释放了。

A:有用!如果在 block 执行以前,self 就释放了,那么 block 的引用计数降为 0,所以自己就会被释放。这样它根本就不会被执行。另外,如果执行一个为 nil 的闭包会导致崩溃。

5.Q:如果在执行 block 的过程中,block 被释放了怎么办?

A:简单来说,block 还会继续执行,但是它捕获的指针会具有不确定的值,详细内容请参考这篇文章

@strongify 和 @weakify


这是ReactiveCocoa 中定义的一个宏。一般可以这样使用:

1
2
3
4
5
@weakify(self);
self.completionHandler = ^(NSInteger result) {
@strongify(self);
[self.property removeObserver: sself forKeyPath:@"pathName"];
};

本文并非分析它们的实现原理,所以就简单解释两点:

  1. 这里的“@”没有任何用处,仅表示强调,这个宏实际上包含了一个空的 AutoreleasePool,这也就是为什么一定要加上“@”。

  2. 它的原理还是和之前一样,生成了一段形如 __weak MyViewController *wself = self; 这种格式的代码:

1
2
#define rac_strongify_(INDEX, VAR) \\
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);

Swift 中的情况


在 Swift 中也有 Strong-Weak Dance 的概念。最简单的方法就是直接沿用 OC 的思路:

1
2
3
4
5
self.completionHandler = { [weak self] in
if let strongSelf = self {
/// ....
}
};

这种写法的缺点在于,我们不能写 if let self = self,因此需要重新定义一个变量 strongSelf,命名方式显得不够优雅。

除此以外还可以使用 Swift 标准库提供的函数 withExtendedLifetime:

1
2
3
4
5
self.completionHandler = { [weak self] in
withExtendedLifetime(self) {
/// ...
}
};

这种写法的缺点在于,self 依然是可选类型的,还需要把它解封后才能使用。

最后,还有一种解决方案是自定义 withExtendedLifetime函数:

1
2
3
4
5
6
7
extension Optional {
func withExtendedLifetime(body: T -> Void) {
if let strongSelf = self {
body(strongSelf)
}
}
}

至于这种写法是否更加优雅,就见仁见智了:

1
2
3
4
5
self.completionHandler = { [weak self] in
self.withExtendedLifetime {
/// 这里用 $0 表示 self
}
};

关于YYKit中block的细节


下面是YYCache中的一段代码:

1
2
3
4
5
6
7
8
9
- (void)_trimRecursively {
__weak typeof(self) _self = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_autoTrimInterval * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0), ^{
__strong typeof(_self) self = _self;
if (!self) return;
[self _trimInBackground];
[self _trimRecursively];
});
}

思考:

如果直接引用 strong self,那 block 创建时就会立刻强引用了 self;而如果先用 weak 引用,则 block 创建时对 self 是弱引用,而直到 block 开始执行时,self 才会被强引用。

如果 block 提交到 queue 但还未执行的时候,整个 Cache 对象被释放了,那这时 weak self 就会变为 nil 了,而后 block 执行的时候 if (!self) return; 就会成立了。

非常严谨!!!

参考


http://www.jianshu.com/p/4ec18161d790

文章目录
  1. 1. 前言
  2. 2. 问题来源
  3. 3. Strong-Weak Dance
  4. 4. 思考
  5. 5. @strongify 和 @weakify
  6. 6. Swift 中的情况
  7. 7. 关于YYKit中block的细节
  8. 8. 参考