入局者,无从脱身。
iOS 编程中循环引用问题是较为常见的一类死局,每个身处江湖的程序员,都难免要面对这个局。
-入局-
循环引用如何触发?
当一个可计数的对象 A 强引用另一个可计数的对象 B 时,B 也强引用 A,即触发循环引用。
说白了就是两个对象互相死拽着对方,谁也不放手,并大喊你先放手,然后就这么争执在那里。(突然觉得有点萌啊 ( ̄▽ ̄))
需要注意的是一个 block 也是一个可计数的对象。
-weak-strong dance 破局-
关于循环引用的面试题其实也不少,一般的解法就是使用 weak-strong dance 的方式。
这名字很形象,招式如下:
// weak-strong dance
__weak typeof(self) weakSelf = self;
self.completion = ^{
__strong typeof(self) strongSelf = weakSelf;
if (strongSelf) {
// do anything you wanna do.
[strongSelf justDoIt];
} else {
// self doesn't exist any more.
}
};
在 weak-strong dance 的破局方式中,招式拆解后就是这样:
-
将 self 指针做 weakify
-
将这个 weakify 后的 self 指针传入 block, 从而保证引用计数不会递增
-
在 block 中将 weakify 的 self 指针做 strongify,从而确保只要 self 沒被释放就可以执行代码
weak-strong dance 通过避免入局来完美的破局,但是有一种异步处理的局 weak-strong dance 无法破。
如果我们期望进行下面这样的异步执行:
异步处理的 block 一定要执行,无论外部其他部分是否还持有。
在这种情况下,weak-strong dance 便无法破局了,因为当 block 在队列中终于可以执行时发现 self 已经被释放了,而 nil 不处理任何 message,预期无法达成。
-block-nil 破局-
此招式命名是我自己起的,便于记忆,理解后也很好配合使用。毕竟每个招数都需要个名字,这样喊出来才能感觉很厉害((。ì _ í。))。
招式如下:
// block-nil
__block typeof(self) blockSelf = self;
self.completion = ^{
// do anything you wanna do directly.
[blockSelf justDoIt];
blockSelf = nil;
};
看懂了吗?我们拆解下招式:
-
先点死穴,触发循环引用
-
然后将 self 设置为 nil 打破了循环引用,破!
在 ARC 情况下,__block 会对可计数的对象做 retain,所以如果只是设置为 __block,其实是触发了循环引用的,而打破循环引用的办法就是将其在结束时设置为 nil。
此法是置死地而后生,与 weak-strong dance 避开入局的方式不同,block-nil 要先入局,再破局。
依据来自官方秘籍「Transitioning to ARC Release Notes」:
In manual reference counting mode,
__block id x;has the effect of not retainingx. In ARC mode,__block id x;defaults to retaining x (just like all other values). To get the manual reference counting mode behavior under ARC, you could use__unsafe_unretained __block id x;. As the name__unsafe_unretainedimplies, however, having a non-retained variable is dangerous (because it can dangle) and is therefore discouraged. Two better options are to either use__weak(if you don’t need to support iOS 4 or OS X v10.6), or set the__blockvalue to nil to break the retain cycle.
其实这是个很老旧的做法了(上古时代的故事),在 MRC -> ARC 时因为对 __block 行为做了变更,所以才可以这样去用。(当然,也许你并没有写过 MRC 的代码,甚至没听说过也不关心,现在年轻人都只顾着吃 Swift 的糖了,谁还吃 Objective-C 这苦菜。)
-局-
每一个技术点都是一个局,一个程序的编写完成中需要你佩戴不同的武器(工具链),用不同的手段(最佳实践),并用身法(设计模式)齐攻之,才能一一击破。
如果说每个 iOS 程序员是一个武林中人,那么循环引用就是每个人身上天生带着的一个弱点,而只要进行练功和修行(coding),就必定入此局。
武功(技术)上没有绝对的旷世奇学(银弹),而你只能不断尝试新的方式来破解自身所处的局,正所谓
破局者,无一法门。
便是这个道理了。
题图:Samurai by Stevan Rodic @Dribbble