循环引用的破局法门

1,092 阅读3分钟
原文链接: mp.weixin.qq.com

入局者,无从脱身。

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 retaining x . 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_unretained  implies, 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 __block  value to nil to break the retain cycle.

其实这是个很老旧的做法了(上古时代的故事),在 MRC -> ARC 时因为对 __block 行为做了变更,所以才可以这样去用。(当然,也许你并没有写过 MRC 的代码,甚至没听说过也不关心,现在年轻人都只顾着吃 Swift 的糖了,谁还吃 Objective-C 这苦菜。)

-局-

每一个技术点都是一个局,一个程序的编写完成中需要你佩戴不同的武器(工具链),用不同的手段(最佳实践),并用身法(设计模式)齐攻之,才能一一击破。

如果说每个 iOS 程序员是一个武林中人,那么循环引用就是每个人身上天生带着的一个弱点,而只要进行练功和修行(coding),就必定入此局。

武功(技术)上没有绝对的旷世奇学(银弹),而你只能不断尝试新的方式来破解自身所处的局,正所谓

破局者,无一法门。

便是这个道理了。


题图:Samurai by Stevan Rodic @Dribbble