内存循环引用算法

平时我们最容易犯的一个错误就是循环引用,而且难以察觉,而FBRetainCycleDetector则是专门在运行期来检查循环引用,那我们来看看他是怎么做到的。

开始

首先,我们来看看在arc环境下,什么时候会发生强引用。

  1. 自身属性,被定义为strong类型的变量,都会产生一次强引用。
  2. associate object,被定义为retain类型的也会被强引用。
  3. block,在被闭包捕获的时候,strong类型对象也会被强引用。
  4. 特殊对象,比如NSTimertarget,集合类型的addObject

associate object

如何记录associate object的持有情况呢?这里要说一下C语言的hook,也就是在链接的时候替换掉objc_setAssociatedObject方法,然后记录源对象和持有对象。关于hook可以参考fishhook这个库。

property

objc的对象会有自身的布局记录(layout),取出每个类中的Ivar的属性,就可以知道哪些属性是强引用的,也就可以知道每个对象所持有的对象了。

block

和property一样,每个block也是有对应的layout的。

其他

其他一些特殊情况,则需要特殊考虑,细节这里就不说明了。

算法

首先,我们将每个需要检测的对象视作一颗颗树,叶子是每个强引用的对象。

作者使用了堆栈来替换递归实现路径点的查找,基本原理是:

// 堆栈用于保存所遍历过的路径
NSMutableArray *stack = [NSMutableArray new];
// 集合用于保存该路径上的所有对象,用于判断是否有对象相等,也就是循环引用了。
NSMutableSet *objectsOnPath = [NSMutableSet new];
// ...
while ([stack count] > 0) {
  // ...
  if ([objectsOnPath containsObject:firstAdjacent]) {
    // 发现循环引用并记录 ...
  } else {
    // ...
    shouldPushToStack = YES;
  }
  [objectsOnPath addObject:top];
  // 如果没有发现循环引用,则查找其子节点,并push进堆栈
  if (shouldPushToStack) {
    if ([stack count] < stackDepth) {
      [stack addObject:firstAdjacent];
    }
  }
  else {
    // 如果已经没有子节点了,就退出堆栈,开始判定父节点的下一个节点
    [stack removeLastObject];
    [objectsOnPath removeObject:top];
  }
}

算法其实很简单,源码也就100行以内。

最后

这种方式给予我们一种能够在运行时检查循环引用的方法,但是这并不代表完全正确,比如CFArray,NSHashMap我们就无法判断子元素的引用情况。还有一些虽然形成了循环引用,但在整个流程的结尾,是必定会解除的,会形成误判。

你可能感兴趣的:(内存循环引用算法)