FBRetainCycleDetector + MLeaksFinder 阅读

FBRetainCycleDetector 是干什么的?

FBRetainCycleDetector 是facebook 开源的,用于检测引起内存泄漏对象的环形引用链。

MLeakFinder 是干什么的?

MLeakFinder 是检测内存中可能发生了内存泄漏的对象。

为什么检测内存泄漏是 FBRetainCycleDetector + MLeaksFinder 的组合?

Facebook 的 FBRetainCycleDetector 是针对可疑对象的引用结构进行树形检测,绘制引用图,可设置遍历的深度,默认是10,可设置更大的遍历深度,整个操作是比较费时的,因此不应该频繁调用,而是有针对性的去使用。而 MLeakFinder 则恰恰是用于找出内存中可能发生内存泄口的可疑对象,MLeakFinder 的操作相对简单,且并不会十分消耗性能。因此经常是FBRetainCycleDetector + MLeaksFinder 的组合使用。

MLeaksFinder 是如何工作的?

阅读从 MLeaksFinder 的源码,找到其中比较核心的部分,来简要说明MLeaksFinder 的工作原理:MLeaksFinder 中有几个比较重要的 category 是 MLeaksFinder work的入口,在 UINavigationController+MemoryLeak.h category +load 方法中hook了

- (void)pushViewController:animated:
- (void)popViewControllerAnimated:popToViewController:animated:
- (void)popToRootViewControllerAnimated:

系统方法, 在方法中去标明一个VC是否应该被释放,在vc被 pop,dismiss 的时候。标志一个 vc 被 “droped”,对于需要延迟释放的 打上 “checkDelay” 的标志,下一次 push 动作时再检测UIViewController+MemoryLeak.h 的 + load 方法中 hook 了

- (void)viewWillAppear:
- (void)viewDidDisappear:
- (void)dismissViewControllerAnimated:completion:

生命周期方法, 在 viewWillAppear 时标记自身 “unDroped”, dismissViewControllerAnimated:completion: 时,标志自身 “droped” viewDidDisappear 时检测自身的 “drop”标志,如果获取到droped 标志,则开始检测自身的内存泄漏问题。

因为,当一个页面退出时并且消失时, 我们认为这个页面应该被释放,如果有需要缓存,下次再行使用的页面 可以不进行检测。

那到底如何检测自身呢,在MLeaksFinder 中 有几个基础的类的自检方式,分别为 UIViewController, UIView , NSObject , 这几个主要的类,这几个基础类名为 MemoryLeak 的 category 都会实现一个 willDealloc 方法 。 在 VC 的viewDidDisappear 的时候去调用VC本身的willDealloc 方法([super willDealloc ]),VC的view([self.view willDealloc])的 willDealloc 方法。UIView 也会同时检测subviews 的 willDealloc。这样则能检测页面的model 层, UI 层 的内存泄漏。UIView , UIViewController 都继承自 NSObject,最终的调用实际上都会走到NSObject 的 willDealloc 方法中。而NSObject 的 willDealloc 方法实现为:

- (BOOL)willDealloc {
 NSString *className = NSStringFromClass([self class]);
 if ([[NSObject classNamesWhitelist] containsObject:className]) return NO; 
  NSNumber *senderPtr = objc_getAssociatedObject([UIApplication sharedApplication], kLatestSenderKey);
 if ([senderPtr isEqualToNumber:@((uintptr_t)self)]) return NO; 
  __weak id weakSelf = self;
 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
    __strong id strongSelf = weakSelf;
   [strongSelf assertNotDealloc];
 });
 return YES;
}

其中最为重要的一段为:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
   __strong id strongSelf = weakSelf;
   [strongSelf assertNotDealloc]; 
});

这个延迟执行的代码是很有心机的,正常页面退出2s后,如果没有内存泄漏,页面的VC,和View 都将会释放,此时 strongSelf 的引用都会变成nil,OC中给nil发消息([strongSelf assertNotDealloc])是不响应的。如果响应了说明还没有释放,那么将成为可疑对象。需要“严加盘问”。

那么 assertNotDealloc 方法 如何严加盘问呢?

此时 MLeaksFinder 是不负责问责,是在哪个环节可能发生了内存泄漏的,因此给开发者以弹框的形式,提示当前对象可能存在内存泄漏情况。点击 “Retain Cycle” 按钮,MLeaksFinder 将调用 FBRetainCycleDetector 进行详细问题检测。

FBRetainCycleDetector 如何检测引用成环?

FBRetainCycleDetector 基于外部传入的object 以及查找深度,进行深度优先遍历所有强引用属性,和动态运行时关联的强引用属性,同时将这些 关联对象的地址 放入 objectSet (set)的集合中, 将对象信息计入 objectOnPath 集合中(set), 并且将对象在对象栈 stack 中存储一份,便于查找对应环。stack 的长度代表了当前遍历的深度。首先判断 如果传入的 object 是 NSObject 的话,获取对象的 class,使用

const char *class_getIvarLayout(Class cls);

获取 class 的所有定义的 property 的布局信息,取出 object 对应的 property 的value值,将value 对象的地址(数字)加入 objectSet 中,将对象指针加入到 objectOnPath,在整个树形遍历中,如果遍历到的新节点,在原来的 objectSet 地址表中已经存在了,代表形成了引用环,即原本的树形结构连成了图。此时可以根据 stack中记录的路径,结合 重复的 object构建一个环形图,作为环形引用链返回。但是,遇到的是NSBlock类型对像,我们首先要知道的是NSBlock在内存中怎么存储的,因此FBRetainCycleDetector 参考了Clang 的文档,对于block的结构定义如下:

struct Block_literal_1 { void *isa; 
// initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock int flags;
  int reserved;
  void (*invoke)(void *, ...);    
  struct Block_descriptor_1 {
     unsigned long int reserved; // NULL unsigned long
     int size; // sizeof(struct Block_literal_1) 
    // optional 
     helper functions void (*copy_helper)(void *dst, void *src); // IFF (1<<25) 
     void (*dispose_helper)(void *src); // IFF (1<<25) 
    // required ABI.2010.3.16 
     const char *signature; // IFF (1<<30) 
} *descriptor; 
// imported variables
};

因此,FBRetainCycleDetector 库中定义了一个和block 结构一致的 struct 名为BlockLiteral,将遇到的block都强转为BlockLiteral,便可以操作block对应的属性和方法BlockLiteral 定义如下:

enum { // Flags from BlockLiteral
  BLOCK_HAS_COPY_DISPOSE =  (1 << 25),
  BLOCK_HAS_CTOR =          (1 << 26), // helpers have C++ code
  BLOCK_IS_GLOBAL =         (1 << 28),
  BLOCK_HAS_STRET =         (1 << 29), // IFF BLOCK_HAS_SIGNATURE
  BLOCK_HAS_SIGNATURE =     (1 << 30),
};

struct BlockDescriptor {
  unsigned long int reserved;                // NULL
  unsigned long int size;
  // optional helper functions
  void (*copy_helper)(void *dst, void *src); // IFF (1<<25)
  void (*dispose_helper)(void *src);         // IFF (1<<25)
  const char *signature;                     // IFF (1<<30)
};

struct BlockLiteral {
  void *isa;  // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
  int flags;
  int reserved;
  void (*invoke)(void *, ...);
  struct BlockDescriptor *descriptor;
  // imported variables
};

虽然知道了block对象的存储结构,知道了在block中哪里记录引用 但哪些对象的存储是强引用,我们任然不知道,在C的结构体中是不存在强弱引用区分的,在编译期,编译器会将所谓的强引用通过一个 copy_helper 的function 做copy 操作,并为 block 生成的 struct 构造 dispose_helper 的 function,dispose_helper 负责在 struct 将要释放时,去释放它所引用的对象。下面是编译器生成的 dispose_helper function 的定义 ,入参为 struct 的地址 _Block_object_dispose 是编译器的 funtion

void __block_dispose_4(struct __block_literal_4 *src) {
     // was _Block_destroy
     _Block_object_dispose(src->existingBlock, BLOCK_FIELD_IS_BLOCK);
}

于是 FBRetainCycleDetector 想到利用黑盒测试,基于原有的 block ,构建了一个新的 block ,将原有的 preference (引用)替换成FBBlockStrongRelationDetector ,FBBlockStrongRelationDetector 是FBRetainCycleDetector 中定义的一个探测器,重写了 release 方法,在收到realese 消息时实际是标记自身的strong标志,对伪装的block对象 即BlockLiteral 对象 利用原有 block 生成的 dispose_helper function 将构建的 BlockLiteral 伪装对象传入 ,模拟一次 relaese 操作,收到 release 消息的对象则是 强引用对象。算法操作草图如下图(手稿图。。。。尴尬。。。):
检测.png

检测核心代码:

  NSMutableSet *> *retainCycles = [NSMutableSet new];
  FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];

  NSMutableArray *stack = [NSMutableArray new];
  NSMutableSet *objectsOnPath = [NSMutableSet new];

  [stack addObject:wrappedObject];

  while ([stack count] > 0) {
    @autoreleasepool {

      FBNodeEnumerator *top = [stack lastObject];

      if (![objectsOnPath containsObject:top]) {
        if ([_objectSet containsObject:@([top.object objectAddress])]) {
          [stack removeLastObject];
          continue;
        }
        [_objectSet addObject:@([top.object objectAddress])];
      }

      [objectsOnPath addObject:top];

        //获取子节点迭代器
      FBNodeEnumerator *firstAdjacent = [top nextObject];
      if (firstAdjacent) {
        //有子节点
        BOOL shouldPushToStack = NO;
        //当前链路上已存在当前子节点
        if ([objectsOnPath containsObject:firstAdjacent]) {
         
          NSUInteger index = [stack indexOfObject:firstAdjacent];
          NSInteger length = [stack count] - index;

          if (index == NSNotFound) {
            shouldPushToStack = YES;
          } else {
            //构建环结构
            NSRange cycleRange = NSMakeRange(index, length);
            NSMutableArray *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
            [cycle replaceObjectAtIndex:0 withObject:firstAdjacent];
            [retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
          }
        } else {
          shouldPushToStack = YES;
        }

        if (shouldPushToStack) {
          if ([stack count] < stackDepth) {
            [stack addObject:firstAdjacent];
          }
        }
      } else {
         //无子节点
        [stack removeLastObject];
        [objectsOnPath removeObject:top];
      }
    }
  }
  return retainCycles;


你可能感兴趣的:(FBRetainCycleDetector + MLeaksFinder 阅读)