有哪些办法可以监测循环引证?

在引证计数的内存管理办法中,因为目标间的引证,最终引证联系图形成“环”才导致循环引证。因而对循环引证的监测直观的主意只需求找到这个环就可以找到循环引证的当地,也便是在有向图中找环(也可以说在树中找环),一起需求找到环中详细的节点,例如 FBRetainCycleDector 便是选用 DFS 进行图中环的检测与查找。

不过还有另一种办法,便是假定目标会很快开释。例如当一个 vcpopdismiss 之后,应该认为该 vc 包含它上面的子 vc,以及它的 viewviewsubView 等,都会被很快开释,假如某个目标没有被开释,就认为该目标内存泄漏了,例如 MLeaksFinder 它的根本原理便是这样。

从实践场景剖析,监测可以从两个方向着手:静态剖析和动态剖析。静态剖析经过将源代码转换成抽象语法树(AST、Abstract Syntax Tree),然后检测出一切违反规矩的代码信息,常见的剖析东西有 Clang Static Analyzer、Infer、OCLint 与 SwiftLint;动态剖析则是在应用运行起来后,剖析其间的内存分配信息,常见的剖析东西有 Instrument-Leaks、Memory Graph、MLeaksFinder、FBRetainCycleDector、OOMDetector 等。

因为开源和典型,就从 MLeaksFinder、FBRetainCycleDector 的源代码入手,看看他们的详细完成计划:

MLeaksFinder

MLeaksFinder 的中心逻辑比较简单:

它运用 Method Swizzle HOOK 了许多 UIKit 相关类,如 UIViewControllerUIViewUINavigationControllerUIPageViewController 等,并拓宽了 NSObject,为其增加 willDealloc 办法。在 UIViewController 或许 UINavigationController 在调用 dismisspop 时,就会调用 vcvc 的子 viewControllersvcviewviewsubViewwillDealloc 办法。运用 weak 与 GCD,在两秒后查看目标是否存在。假如存在就会敞开一个弹窗,依据宏界说选择输出运用 FBRetainCycleDetector 查找出来的循环引证链。

- (BOOL)willDealloc {
  NSString *className = NSStringFromClass([self class]);
  if ([[NSObject classNamesWhitelist] containsObject:className])
    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;
}

但这样的内存办法存在两种办法的“误判”:

  • 单例或许被缓存起来的 viewvc
  • 开释不及时的 view 或许 vc

对此,MLeaksFinder 也进行了一系列的措施进行补救:

  • 经过 assert 确保调用在主线程,按 vcvc 的子 viewcontrollersvcview 这样的顺序调用它们的 willDealloc,并其向主线程追加是否存在目标(willDealloc)的任务。因为主线程是串行队列,因而 GCD 的回调总也是按顺序调用的。
NSAssert([NSThread isMainThread], @"Must be in main thread.");
  • 引入相关目标 parentPtrs,在上述顺序调用(至上而下)的进程中将 vc 或许 view 的一切未被开释的父级目标存储。
- (void)willReleaseObject:(id)object relationship:(NSString *)relationship {
    ...
  // 存储父级目标
   [object setParentPtrs:[[self parentPtrs] setByAddingObject:@((uintptr_t)object)]];
   ...
}
  • 引入了静态目标 leakedObjectPtrs,将最优先回调的目标(最上层的目标)加入到 leakedObjectPtrs 中,假如 parentPtrsleakedObjectPtrs 有相同的交集,就不会弹窗,直接退出,这也确保了在同一个循环引证中只有一个弹窗的调用。假如目标被销毁(或许是开释不及时的 vc 或许 view)了,则将其地址从 leakedObjectPtrs 移除。
+ (BOOL)isAnyObjectLeakedAtPtrs:(NSSet *)ptrs {
  NSAssert([NSThread isMainThread], @"Must be in main thread.");
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    leakedObjectPtrs = [[NSMutableSet alloc] init];
   });
  
  if (!ptrs.count) {
    return NO;
   }
  // 发生交集, 之前有弹窗, 直接退出
  if ([leakedObjectPtrs intersectsSet:ptrs]) {
    return YES;
   } else {
    return NO;
   }
}
​
- (void)dealloc {
  NSNumber *objectPtr = _objectPtr;
  NSArray *viewStack = _viewStack;
  // 目标假如被开释, 从 leakedObjectPtrs 将其移出
  dispatch_async(dispatch_get_main_queue(), ^{
     [leakedObjectPtrs removeObject:objectPtr];
     [MLeaksMessenger alertWithTitle:@"Object Deallocated"
                message:[NSString stringWithFormat:@"%@", viewStack]];
   });
}

单例或许被缓存起来的 viewvc 来说,因为 leakedObjectPtrs 留有一份地址,所以当重复进入、退出页面时,不会重复进行弹窗;

关于开释不及时的 view 或许 vc 来说,在未被开释前,会发生弹窗,在开释之后,弹出的信息变为 Object Deallocated,也便是不只会重复弹窗,并且还有新加弹窗。这是因为关于 vcview 来说,它们的内存往往占用较大,因而应该立即被开释,如网络回调中 block 的强持有,这种情况就应把强引证改为弱引证;

关于真实循环引证的目标,因为每次都会创建新的目标,因而会重复弹窗;

不过 MLeaksFinder 的缺陷也很明显,大部分只能用来对它做 viewvc 的循环引证监测,关于 C/C++ 的内存泄漏,以及自界说目标维护本钱较高,算是一个轻量级的计划。

FBRetainCycleDector

根本运用

供给一个目标,FBRetainCycleDector 就能以这个目标作为开端节点查找循环引证链,一起还可以依据需求传入 configuration 装备项,包含是否监测 timer、是否包含 block 地址以及自界说过滤强引证链等内容。在 MLeaksFinder 是这样运用的:

#if _INTERNAL_MLF_RC_ENABLED
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self.object];
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];
​
BOOL hasFound = NO;
for (NSArray *retainCycle in retainCycles) {
  NSInteger index = 0;
  for (FBObjectiveCGraphElement *element in retainCycle) {
    // 查找特定的循环引证链
    if (element.object == object) {
      NSArray *shiftedRetainCycle = [self shiftArray:retainCycle toIndex:index];
​
      dispatch_async(dispatch_get_main_queue(), ^{
         [MLeaksMessenger alertWithTitle:@"Retain Cycle"
                    message:[NSString stringWithFormat:@"%@", shiftedRetainCycle]];
       });
      hasFound = YES;
      break;
     }
​
    ++index;
   }
  if (hasFound) {
    break;
   }
}
if (!hasFound) {
  dispatch_async(dispatch_get_main_queue(), ^{
     [MLeaksMessenger alertWithTitle:@"Retain Cycle"
                message:@"Fail to find a retain cycle"];
   });
}
#endif

关于相关目标,因为其在目标的内存布局中不存在,FBRetainCycleDector 选用 fishhook 追踪了 objc_setAssociatedObjectobjc_resetAssociatedObjects 办法,使得关于相关目标的循环引证得以捕获。但需求尽早进行 hook,例如在 main.m 中:

#import <FBRetainCycleDetector/FBAssociationManager.h>int main(int argc, char * argv[]) {
 @autoreleasepool {
   [FBAssociationManager hook];
  return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
  }
}

总览

FBRetainCycleDetector 围绕着 FBRetainCycleDetector 类展开,经过初始化传入 configuration 以及后续增加待监测目标 addCandidate,这时整个目标便以构建完成。最终执行 findRetainCycles 办法进行循环引证查找并回来循环引证链。它的中心类图如下:

iOS中的内存管理|循环引用的监测(MLeaksFinder&FBRetainCycleDector)

传入的目标会被封装为 FBObjectiveCGraphElement 类型,一起它有三个子类 FBObjectiveCBlockFBObjectiveCObjectFBObjectiveCNSCFTimer。这是因为需求弱引证待检测目标,一起不同目标的内存布局不同(如分为一般 NSObject 目标,block 目标,timer 目标),不只如此封装还可以加入更多的目标细节以便开发者排查。

FBObjectiveCGraphElement 封装了获取相关目标强引证的相关逻辑,FBObjectiveCBlock 封装了 block 目标强引证的相关逻辑,FBObjectiveCObject 封装了 NSObject 目标强引证的相关逻辑,FBObjectiveCNSCFTimer 封装了 NSTimer 目标强引证的相关逻辑。

算法剖析

查找循环引证的算法逻辑首要集中在 _findRetainCyclesInObject 办法中,拜访树中每一条途径查看是否有循环引证,它以栈替代了递归计划,防止了多次递归导致的栈溢出,一起每次出栈时运用 autoreleasepool 防止了在这之中的发生的大量临时目标形成的内存激增。选用迭代器 FBNodeEnumerator 包装每一个节点,这样在迭代器内部保存未入栈的目标,便于查找当时途径的循环引证链,一起也防止一下将大量的子节点都入栈,提高查找功率。

重点介绍出栈时的相关逻辑:

  • 取出栈顶目标

  • 假如当时目标未被拜访过但之前查找过,阐明已拜访过相关子树,则直接出栈;

  • 取出栈顶目标的下一个子节点:

    • 假如目标为空阐明当时目标无下一个子节点,直接出栈;
    • 假如当时目标在 objectsOnPath 不存在,阐明引证联系正常,将子节点入栈;
    • 假如当时目标之前拜访过,阐明有循环引证,栈顶到之前拜访过的节点之前的目标全都是循环引证联系链的节点,将其保存一起并不入栈防止重复入栈形成死循环。

例如下图在节点 7 作为栈顶目标时,此时 objectsOnPath[1, 2, 4, 7],节点 7 的子目标 2 出现过,那么从 [2, 4, 7] 则是循环引证链的相关目标。

iOS中的内存管理|循环引用的监测(MLeaksFinder&FBRetainCycleDector)

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement
                                 stackDepth:(NSUInteger)stackDepth
{
 NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [NSMutableSet new];
 FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];
​
 NSMutableArray<FBNodeEnumerator *> *stack = [NSMutableArray new];
 // 当时拜访的途径
 NSMutableSet<FBNodeEnumerator *> *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<FBNodeEnumerator *> *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;
}

怎么获取目标强引证指针

FBObjectiveCGraphElementallRetainedObjects 办法回来了目标的一切的强引证。上文也提到过,不同的目标有不同的获取完成,这也是可以监测循环引证的关键:

获取 NSObject 的强引证:

获取 NSObject 目标的强引证在 FBObjectiveCObject 类中完成,它运用 Runtime 的一些函数获得了 ivarsivarLayout(区分了哪些是强引证和弱引证),它的几个中心办法逻辑如下(按调用顺序):

allRetainedObjects:获取类的强引证布局信息,经过 object_getIvar (OC目标)或偏移(结构体)得到实践的目标,并将其封装为 FBObjectiveCGraphElement 类型,最终对是否是桥接目标,元类目标,可枚举目标进行处理。

- (NSSet *)allRetainedObjects
{
  ...
 // 获取类的强引证布局信息
 NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);
​
 NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];
​
 for (id<FBObjectReference> ref in strongIvars) {
  // ref 存储了 class 强引证的相关信息, 经过 object_getIvar(OC目标)或偏移(结构体)得到实践的目标
  id referencedObject = [ref objectReferenceFromObject:self.object];
​
  if (referencedObject) {
   NSArray<NSString *> *namePath = [ref namePath];
   FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, referencedObject, self.configuration, namePath);
   if (element) {
     [retainedObjects addObject:element];
    }
   }
  }
​
 if ([NSStringFromClass(aCls) hasPrefix:@"__NSCF"]) {
  return [NSSet setWithArray:retainedObjects];
  }
​
 if (class_isMetaClass(aCls)) {
  return nil;
  }
​
 if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {
    ... 
  }
}

FBGetObjectStrongReferences:从子类到父类顺次获取强引证,由 FBObjectReference 封装,并进行缓存。

NSArray<id<FBObjectReference>> *FBGetObjectStrongReferences(id obj,
                              NSMutableDictionary<Class, NSArray<id<FBObjectReference>> *> *layoutCache) {
 NSMutableArray<id<FBObjectReference>> *array = [NSMutableArray new];
​
 __unsafe_unretained Class previousClass = nil;
 __unsafe_unretained Class currentClass = object_getClass(obj);
 // 从子类到父类顺次获取
 while (previousClass != currentClass) {
  NSArray<id<FBObjectReference>> *ivars;
  // 假如之前缓存过
  if (layoutCache && currentClass) {
   ivars = layoutCache[currentClass];
   }
  
  if (!ivars) {
   // 获取类的强引证布局信息
   ivars = FBGetStrongReferencesForClass(currentClass);
   if (layoutCache && currentClass) {
    layoutCache[(id<NSCopying>)currentClass] = ivars;
    }
   }
   [array addObjectsFromArray:ivars];previousClass = currentClass;
  currentClass = class_getSuperclass(currentClass);
  }
​
 return [array copy];
}

FBGetStrongReferencesForClass

从类中获取它指向的一切引证,包含强引证和弱引证(FBGetClassReferences办法);

经过 class_getIvarLayout 获取关于 ivar 的描绘信息;经过 FBGetMinimumIvarIndex 获取 ivar 索引的最小值;经过 FBGetLayoutAsIndexesForDescription 获取一切强引证的 Range;

最终运用 NSPredicate 过滤一切不在强引证 Range 中的 ivar

static NSArray<id<FBObjectReference>> *FBGetStrongReferencesForClass(Class aCls) {
 // 获取类的一切引证信息
 NSArray<id<FBObjectReference>> *ivars = [FBGetClassReferences(aCls) filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
  if ([evaluatedObject isKindOfClass:[FBIvarReference class]]) {
   FBIvarReference *wrapper = evaluatedObject;
   // 过滤 typeEncode 不为目标类型的数据
   return wrapper.type != FBUnknownType;
   }
  return YES;
  }]];
 // 获取 ivar 的描绘信息
 const uint8_t *fullLayout = class_getIvarLayout(aCls);
​
 if (!fullLayout) {
  return @[];
  }
 // 获取 ivar 索引的最小值
 NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
 // 经过 fullLayout 和 minimumIndex 获取一切强引证的 Range
 NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);
 // 过滤掉弱引证 ivar
 NSArray<id<FBObjectReference>> *filteredIvars =
  [ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
                                      NSDictionary *bindings) {
  return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
  }]];
​
 return filteredIvars;
}

FBGetLayoutAsIndexesForDescription

经过 fullLayoutminimumIndex 获取一切强引证的 Range。fullLayout 它以若干组 \xnm 方式表明,n 表明 n 个非强特点,m 表明有 m 个强特点;

minimumIndex 表明开端位,upperNibble 表明非强引证数量,因而需求加上 upperNibbleNSMakeRange(currentIndex, lowerNibble) 便是强引证的规模,最终再加上 lowerNibble 略过强引证索引与下一个数组开端对齐。

static NSIndexSet *FBGetLayoutAsIndexesForDescription(NSUInteger minimumIndex, const uint8_t *layoutDescription) {
 NSMutableIndexSet *interestingIndexes = [NSMutableIndexSet new];
 NSUInteger currentIndex = minimumIndex;
​
 while (*layoutDescription != '\x00') {
  int upperNibble = (*layoutDescription & 0xf0) >> 4;
  int lowerNibble = *layoutDescription & 0xf;
​
  currentIndex += upperNibble;
   [interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
  currentIndex += lowerNibble;
​
  ++layoutDescription;
  }
​
 return interestingIndexes;
}

FBGetClassReferences

调用 Runtime 的 class_copyIvarList 获取类的一切 ivar,并封装成 FBIvarReference 目标,其间包含了实例变量名称、类型(依据 typeEncoding 区分)、偏移、索引等信息;假如是结构体则遍历查看它是否包含其他的目标(FBGetReferencesForObjectsInStructEncoding 办法)。

NSArray<id<FBObjectReference>> *FBGetClassReferences(Class aCls) {
 NSMutableArray<id<FBObjectReference>> *result = [NSMutableArray new];
​
 unsigned int count;
 Ivar *ivars = class_copyIvarList(aCls, &count);
​
 for (unsigned int i = 0; i < count; ++i) {
  Ivar ivar = ivars[i];
  FBIvarReference *wrapper = [[FBIvarReference alloc] initWithIvar:ivar];
  // 结构体类型,再遍历其间的结构
  if (wrapper.type == FBStructType) {
   std::string encoding = std::string(ivar_getTypeEncoding(wrapper.ivar));
   NSArray<FBObjectInStructReference *> *references = FBGetReferencesForObjectsInStructEncoding(wrapper, encoding);[result addObjectsFromArray:references];
   } else {
    [result addObject:wrapper];
   }
  }
 free(ivars);
​
 return [result copy];
}

获取 block 的强引证:

获取 block 目标的强引证在 FBObjectiveCBlock 类中完成,它运用了 dispose_helper 函数会向强引证目标发送 release 音讯完成,而对弱引证不会做任何处理,下面是一些首要函数与类:

allRetainedObjects:获取 block 的强引证数组(FBGetBlockStrongReferences 办法),并封装为 FBObjectiveCGraphElement 类型,这与 FBObjectiveCObject 的处理进程相似。

- (NSSet *)allRetainedObjects
{
 NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];
 
 __attribute__((objc_precise_lifetime)) id anObject = self.object;
​
 void *blockObjectReference = (__bridge void *)anObject;
 NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);
​
 for (id object in allRetainedReferences) {
  FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
  if (element) {
    [results addObject:element];
   }
  }
​
 return [NSSet setWithArray:results];
}

FBGetBlockStrongReferences:获取强引证的下标(_GetBlockStrongLayout 办法),将 block 转换为 void **blockReference,然后经过下标获取到强引证目标。

NSArray *FBGetBlockStrongReferences(void *block) {
 if (!FBObjectIsBlock(block)) {
  return nil;
  }
​
 NSMutableArray *results = [NSMutableArray new];
​
 void **blockReference = block;
 NSIndexSet *strongLayout = _GetBlockStrongLayout(block);
  [strongLayout enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL *stop) {
  void **reference = &blockReference[idx];
​
  if (reference && (*reference)) {
   id object = (id)(*reference);
​
   if (object) {
     [results addObject:object];
    }
   }
  }];
​
 return [results autorelease];
}

FBBlockStrongRelationDetector:在运用 dispose_helper 时运用的伪装成被引证的类,重写了 release 办法(仅仅做符号,不真实 release),一起含有一些必要特点兼容了引证目标是 block 的情况。

@implementation FBBlockStrongRelationDetector
+ (id)alloc
{
 FBBlockStrongRelationDetector *obj = [super alloc];
​
 // 伪装成 block 
 obj->forwarding = obj;
 obj->byref_keep = byref_keep_nop;
 obj->byref_dispose = byref_dispose_nop;
​
 return obj;
}
​
- (oneway void)release
{
 _strong = YES;
}
​
- (oneway void)trueRelease
{
  [super release];
}
​
@end

_GetBlockStrongLayout

假如有 C++ 的结构解析器,阐明它持有的目标或许没有依照指针巨细对齐,或许假如没有 dispose 函数,阐明它不会持有目标,这两种情况直接回来 nil

block 转化为 BlockLiteral 类型,获得 block 所占内存巨细,除以函数指针巨细,并向上取整,得到或许有的引证目标个数(实践必定小于这个数,因为含有 block 本身的一些特点,如 isaflagsize 等)。

不过因为其指针对齐与捕获变量排序机制(一般按__strong`__block__weak 排序),咱们以此创建的 FBBlockStrongRelationDetector 数组也与强引证的地址对齐,调用 dispose_helper 并将被符号的下标保留并回来。

static NSIndexSet *_GetBlockStrongLayout(void *block) {
 struct BlockLiteral *blockLiteral = block;
 
 if ((blockLiteral->flags & BLOCK_HAS_CTOR)
   || !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
  return nil;
  }
​
 void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;
 const size_t ptrSize = sizeof(void *);
​
 const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;
​
 void *obj[elements];
 void *detectors[elements];
​
 for (size_t i = 0; i < elements; ++i) {
  FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
  obj[i] = detectors[i] = detector;
  }
​
 @autoreleasepool {
  dispose_helper(obj);
  }
​
 NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
​
 for (size_t i = 0; i < elements; ++i) {
  FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
  if (detector.isStrong) {
    [layout addIndex:i];
   }
   [detector trueRelease];
  }
​
 return layout;
}

获取 NSTimer 的强引证:

获取 NSTimer 的强引证在 FBObjectiveCNSCFTimer 中完成,它将 NSTimer 转换为 CFTimer,假如它有 retain 函数,就假定它含有强引证目标,将 targetuserInfo 分别将其以 FBObjectiveCGraphElement 包装并回来。

- (NSSet *)allRetainedObjects
{
 __attribute__((objc_precise_lifetime)) NSTimer *timer = self.object;
​
 if (!timer) {
  return nil;
  }
​
 NSMutableSet *retained = [[super allRetainedObjects] mutableCopy];
​
 CFRunLoopTimerContext context;
 CFRunLoopTimerGetContext((CFRunLoopTimerRef)timer, &context);
​
 if (context.info && context.retain) {
  _FBNSCFTimerInfoStruct infoStruct = *(_FBNSCFTimerInfoStruct *)(context.info);
  if (infoStruct.target) {
   FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.target, self.configuration, @[@"target"]);
   if (element) {
     [retained addObject:element];
    }
   }
  if (infoStruct.userInfo) {
   FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self, infoStruct.userInfo, self.configuration, @[@"userInfo"]);
   if (element) {
     [retained addObject:element];
    }
   }
  }
​
 return retained;
}

获取强引证相关目标:

FBAssociationManager 中供给了 hook 相关目标的 objc_setAssociatedObjectobjc_removeAssociatedObjects,它将设置成 retain 战略的相关目标的 key 复制存储,最终经过复制的强引证的 key 经过 objc_getAssociatedObject 取出强引证(associationsForObject)。

 NSArray *associations(id object) {
  std::lock_guard<std::mutex> l(*_associationMutex);
  if (_associationMap->size() == 0 ){
   return nil;
   }
​
  auto i = _associationMap->find(object);
  if (i == _associationMap->end()) {
   return nil;
   }
​
  auto *refs = i->second;
​
  NSMutableArray *array = [NSMutableArray array];
  for (auto &key: *refs) {
   // 找到备份的 key,从相关目标中取出强引证
   id value = objc_getAssociatedObject(object, key);
   if (value) {
     [array addObject:value];
    }
   }
  return array;
  }
​
 static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
   {
   std::lock_guard<std::mutex> l(*_associationMutex);
   // 复制一份 key
   if (policy == OBJC_ASSOCIATION_RETAIN ||
     policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
    _threadUnsafeSetStrongAssociation(object, key, value);
    } else {
    // 或许是战略改动
    _threadUnsafeResetAssociationAtKey(object, key);
    }
   }
  fb_orig_objc_setAssociatedObject(object, key, value, policy);
  }

可迭代目标怎么处理

假如目标支撑 NSFastEnumeration 协议,会遍历目标,将容器里的内容取出,以 FBObjectiveCGraphElement 封装。不过遍历进程中元素或许会改动,因而会假如取出失败会进行最大次数为 10 的重试机制。

NSInteger tries = 10;
for (NSInteger i = 0; i < tries; ++i) {
 NSMutableSet *temporaryRetainedObjects = [NSMutableSet new];
 @try {
  for (id subobject in self.object) {
   if (retainsKeys) {
    FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, subobject, self.configuration);
    if (element) {
      [temporaryRetainedObjects addObject:element];
     }
    }
   if (isKeyValued && retainsValues) {
    FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self,
                                   [self.object objectForKey:subobject],self.configuration);
    if (element) {
      [temporaryRetainedObjects addObject:element];
     }
    }
   }
  }
 @catch (NSException *exception) {
  continue;
  }
  [retainedObjects addObjectsFromArray:[temporaryRetainedObjects allObjects]];
 break;
}

本文同步发布于公众号(nihao的编程随记)和个人博客nihao,欢迎 follow 以获得更佳观看体会。

[1] iOS端循环引证检测实战

[2] MLeaksFinder新特性

[3] Automatic memory leak detection on iOS

[4] FBRetainCycleDetector

[5] 检测 NSObject 目标持有的强指针

[6] iOS 中的 block 是怎么持有目标的

[7] runtime运用篇:class_getIvarLayout 和 class_getWeakIvarLayout

[8] Swift静态代码监测工程实践

[9] 聊聊循环引证的监测

[10] ObjC中的TypeEncodings