开篇叨叨

FBRetainCycleDetector 是FaceBook推出的用于检测对象是否有循环引用的一个开源库.下面是一个简单的用法:

#import <FBRetainCycleDetector/FBRetainCycleDetector.h>

FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);

它先创建一个FBRetainCycleDetector循环引用检测器,然后将待检查循环引用关系的对象通过addCandidate传入,检测器通过findRetainCycles查找这个对象中的循环引用。它能通过添加Filters对某些进行过滤,并且支持检测Block,Timmer,Associations这些对象的内存泄漏。但是它不能单独在项目中使用必须借助其他工具比如FBAllocationTracker,MLeaksFinders 查找Candidates,下面这篇博客主要是对FBRetainCycleDetector源码进行分析,不对使用做过多介绍,具体使用可以在GitHub主页查看。

FBRetainCycleDetector

项目结构

下图是FBRetainCycleDetector目录结构:

源码解析

FBRetainCycleDetector主要任务有两个:

1. 找出****Object********Timer类型Object********Object associate类型Object****,****Block类型Object**** 这几种类型的 Strong Reference。
2. 最开始把Self作为根节点,沿着找出的各个Reference进行深度遍历,如果形成了环,则存在循环依赖。

OK 我们现在开始FBRetainCycleDetector的解析:

FBRetainCycleDetector 对象初始化

首先我们来看下FBRetainCycleDetector:

@implementation FBRetainCycleDetector {
NSMutableArray *_candidates;
FBObjectGraphConfiguration *_configuration;
NSMutableSet *_objectSet;
}

这里最重要有两个对象,_candidates是一系列待检测的对象,_configuration为配置对象,在这里可以配置需要过滤的对象,检测属性的配置等等。

为了简化代码的分析过程,我们以最简单的初始化形式入手看下初始化过程我们做了什么:

- (instancetype)init {
return [self initWithConfiguration:
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:FBGetStandardGraphEdgeFilters()
shouldInspectTimers:YES]];
}
- (instancetype)initWithConfiguration:(FBObjectGraphConfiguration *)configuration {
if (self = [super init]) {
_configuration = configuration;
_candidates = [NSMutableArray new];
_objectSet = [NSMutableSet new];
}
return self;
}

在初始化过程中FBRetainCycleDetector调用了initWithConfiguration:初始化了一个标准的过滤器,过滤器过滤了引用循环中的一些类和方法,并检查NSTimer循环引用情况。

添加检测对象

待检测的对象在addCandidate方法中被封装为FBObjectiveCGraphElement对象,然后添加到_candidates中。

- (void)addCandidate:(id)candidate {
FBObjectiveCGraphElement *graphElement = FBWrapObjectGraphElement(nil, candidate, _configuration);
if (graphElement) {
[_candidates addObject:graphElement];
}
}

查找循环引用

FBRetainCycleDetector 通过调用findRetainCycles开始查找循环引用,如果某个对象比较复杂可能检测时间会比较久,所以在FBRetainCycleDetector中默认设置了一个检测深度,默认情况下为10.可以通过

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length

来指定更深的层级。我们继续看findRetainCycles,findRetainCycles内部只是简单得转调了findRetainCyclesWithMaxCycleLength方法。

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCycles {
return [self findRetainCyclesWithMaxCycleLength:kFBRetainCycleDetectorDefaultStackDepth];
}

我们继续跟进findRetainCyclesWithMaxCycleLength方法:

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length {
NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *allRetainCycles = [NSMutableSet new];
for (FBObjectiveCGraphElement *graphElement in _candidates) {
NSSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [self _findRetainCyclesInObject:graphElement
stackDepth:length];
[allRetainCycles unionSet:retainCycles];
}
[_candidates removeAllObjects];
[_objectSet removeAllObjects];

// Filter cycles that have been broken down since we found them.
// These are false-positive that were picked-up and are transient cycles.
NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *brokenCycles = [NSMutableSet set];
for (NSArray<FBObjectiveCGraphElement *> *itemCycle in allRetainCycles) {
for (FBObjectiveCGraphElement *element in itemCycle) {
if (element.object == nil) {
// At least one element of the cycle has been removed, thus breaking
// the cycle.
[brokenCycles addObject:itemCycle];
break;
}
}
}
[allRetainCycles minusSet:brokenCycles];

return allRetainCycles;
}

findRetainCyclesWithMaxCycleLength方法中主要可以分成两个阶段,第一个阶段通过_findRetainCyclesInObject查找被加入到_candidates中对象的循环引用放到allRetainCycles集合中 ,第二阶段是查看这些元素中哪些是已经释放了的从allRetainCycles中移除。

对于对象的相互引用情况可以看做一个有向图,对象之间的引用就是图的的连线,每一个对象就是有向图的顶点,查找循环引用的过程就是在整个有向图中查找闭环的过程,FBRetainCycleDetector是通过DFS深度遍历的方式遍历整个对象有向图的,整个遍历查找过程真正的实现位于
_findRetainCyclesInObject方法中,在分析_findRetainCyclesInObject实现之前我们先通过下面的视频看下整个算法的大概流程:

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement
stackDepth:(NSUInteger)stackDepth {

NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [NSMutableSet new];

// 查找循环引用是通过深度遍历整个对象图来实现的
// 首先初始化深度搜索树中的一个节点
FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];

// stack 用于保存当前DFS搜索树中的搜索路径
NSMutableArray<FBNodeEnumerator *> *stack = [NSMutableArray new];

// objectsOnPath 保存搜索路径中访问过的对象
NSMutableSet<FBNodeEnumerator *> *objectsOnPath = [NSMutableSet new];

// 增加根节点,从根节点开始搜索
[stack addObject:wrappedObject];

//判断是否已经搜索完毕
while ([stack count] > 0) {
// 算法会创建许多生命周期非常短的对象,这个会造成很大的内存抖动,所以这里使用了自动释放池来缓解这个问题。
@autoreleasepool {
// 取出stack中的最上面的节点,并标记该节点已读
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];

// Take next adjecent node to that child. Wrapper object can
// persist iteration state. If we see that node again, it will
// give us new adjacent node unless it runs out of them
//取top节点的next节点,也就是这个object可能持有的对象。
FBNodeEnumerator *firstAdjacent = [top nextObject];
if (firstAdjacent) {
//如果存在未访问到的节点
// Current node still has some adjacent not-visited nodes

BOOL shouldPushToStack = NO;

// 检查是否已经访问过了
// Check if child was already seen in that path
if ([objectsOnPath containsObject:firstAdjacent]) {
// We have caught a retain cycle
//如果该节点已经存在被访问过的对象中,说明构成了retain cycle
// Ignore the first element which is equal to firstAdjacent, use firstAdjacent
// we're doing that because firstAdjacent has set all contexts, while its
// first occurence could be a root without any context
NSUInteger index = [stack indexOfObject:firstAdjacent];
NSInteger length = [stack count] - index;

if (index == NSNotFound) {
// Object got deallocated between checking if it exists and grabbing its index
shouldPushToStack = YES;
} else {
//计算出firstAdj出现的位置,同时计算出路径的长度,将这一系列的节点(object),也就是环,存放在array里面。
//将这个array存放到retainCycles集合中。
NSRange cycleRange = NSMakeRange(index, length);
NSMutableArray<FBNodeEnumerator *> *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
[cycle replaceObjectAtIndex:0 withObject:firstAdjacent];

// 1. Unwrap the cycle
// 2. Shift to lowest address (if we omit that, and the cycle is created by same class,
// we might have duplicates)
// 3. Shift by class (lexicographically)

[retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
}
} else {
// Node is clear to check, add it to stack and continue
shouldPushToStack = YES;
}

if (shouldPushToStack) {
if ([stack count] < stackDepth) {
[stack addObject:firstAdjacent];
}
}
} else {
// Node has no more adjacent nodes, it itself is done, move on
[stack removeLastObject];
[objectsOnPath removeObject:top];
}
}
}
return retainCycles;
}

_findRetainCyclesInObject方法中有四个比较重要的对象:

* retainCycles用于存放循环引用环的集合;
* wrappedObject图的根起点
* stack是在图中当前的路径
* objectsOnPath用于记录以前访问过的节点。

在遍历开始前首先用传进来的graphElement来初始化FBNodeEnumerator对象,FBNodeEnumerator 是整个遍历的关键,但是这里我们先注重整个流程,开始遍历之前会将用当前graphElement初始化的FBNodeEnumerator添加到stack,然后通过FBNodeEnumerator 的 nextObject 取出下一个节点,通过 [objectsOnPath containsObject:firstAdjacent] 来判断该节点是否已经被访问过了,如果该节点已经存在被访问过的对象中,说明构成了retain cycle,这时候计算出firstAdj出现的位置,同时计算出路径的长度,将这一系列的节点组成的环,存放在array里面。将这个array存放到retainCycles集合中。

这里还有两个比较重要的方法_unwrapCycle和_shiftToUnifiedCycle:

  • _unwrapCycle: 的作用是将数组中的每一个 FBNodeEnumerator 实例解压成 FBObjectiveCGraphElement
  • _shiftToUnifiedCycle: 方法用于将每一个环中的元素按照地址递增以及字母顺序来排序,来避免把相同环的不同表示方式当作两个不同的环。

获取强引用对象

我们上面介绍addCandidate方法的时候看到FBRetainCycleDetector会把所有的对象封装在FBObjectiveCGraphElement中,FBObjectiveCGraphElement类中有个十分重要的方法allRetainedObjects,它用于返回某个对象所持有的全部强引用对象数组。获取数组之后,再把其中的对象包装成新的FBNodeEnumerator实例,作为下一个有向图的顶点。

- (NSSet *)allRetainedObjects {
NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object];
NSMutableSet *retainedObjects = [NSMutableSet new];

for (id obj in retainedObjectsNotWrapped) {
FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
obj,
_configuration,
@[@"__associated_object"]);
if (element) {
[retainedObjects addObject:element];
}
}
return retainedObjects;
}

在最开始我们会通过[FBAssociationManager associationsForObject:]获取该对象所有通过objc_setAssociatedObject关联的对象。FBAssociationManager 是 object associations的一个跟踪器,通过指定对象它能够返回该指定对象通过objc_setAssociatedObject retain策略添加的关联对象。它只有三个方法:

/**
开始跟踪 object associations,这里使用的是fishhook来hook objc_(set/remove)AssociatedObject 这些C方法,并插入一些跟踪代码
*/
+ (void)hook;

/**
停止跟踪 object associations
*/
+ (void)unhook;

/**
返回指定对象的 objects associated
*/
+ (nullable NSArray *)associationsForObject:(nullable id)object;

要跟踪关联对象必须在main.m中调用[FBAssociationManager hook]通过fishhook Hook 对应方法。

我们接下来继续看下allRetainedObjects方法。allRetainedObjects方法中会遍历FBAssociationManager获取到的_object关联的对象。然后通过FBWrapObjectGraphElementWithContext创建FBObjectiveCGraphElement。并添加到retainedObjects数组返回。

FBObjectiveCGraphElement *FBWrapObjectGraphElementWithContext(FBObjectiveCGraphElement *sourceElement,
id object,
FBObjectGraphConfiguration *configuration,
NSArray<NSString *> *namePath) {
//通过FBObjectGraphConfiguration中添加的过滤器对当前的object进行一次过滤
if (_ShouldBreakGraphEdge(configuration, sourceElement, [namePath firstObject], object_getClass(object))) {
return nil;
}
FBObjectiveCBlock *newElement;
//如果是Block类型则返回FBObjectiveCBlock
if (FBObjectIsBlock((__bridge void *)object)) {
newElement = [[FBObjectiveCBlock alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
//如果是NSTimer的子类并且配置类中shouldInspectTimers 为 YES 返回FBObjectiveCNSCFTimer
if ([object_getClass(object) isSubclassOfClass:[NSTimer class]] &&
configuration.shouldInspectTimers) {
newElement = [[FBObjectiveCNSCFTimer alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
//否则返回FBObjectiveCObject
newElement = [[FBObjectiveCObject alloc] initWithObject:object
configuration:configuration
namePath:namePath];
}
}
return (configuration && configuration.transformerBlock) ? configuration.transformerBlock(newElement) : newElement;
}

FBWrapObjectGraphElementWithContext会根据传入的object的对象判断,如果是Block类型则返回FBObjectiveCBlock,如果是NSTimer的子类并且配置类中shouldInspectTimers 为 YES 返回FBObjectiveCNSCFTimer,如果其他类型返回FBObjectiveCObject。

FBObjectiveCBlock,FBObjectiveCNSCFTimer,FBObjectiveCObject都是FBObjectiveCGraphElement的子类,都是一种对象图元素。

我们回到FBNodeEnumerator类,我们上面提到深度遍历对象树靠的就是FBNodeEnumerator的nextObject方法。我们看下nextObject方法:

- (FBNodeEnumerator *)nextObject {
if (!_object) {
return nil;
} else if (!_retainedObjectsSnapshot) {
_retainedObjectsSnapshot = [_object allRetainedObjects];
_enumerator = [_retainedObjectsSnapshot objectEnumerator];
}
FBObjectiveCGraphElement *next = [_enumerator nextObject];
if (next) {
return [[FBNodeEnumerator alloc] initWithObject:next];
}
return nil;
}

在这里最关键的就是调用了上面提到的allRetainedObjects方法。也就是说通过allRetainedObjects可以对当前节点进行展开递归。

我们针对上面提到的三种对象图元素一一看下怎么获取到某个对象的全部强引用属性:

FBObjectiveCBlock

- (NSSet *)allRetainedObjects {

// ......
//获取一个对象的所有强引用属性
NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);
//通过super方法获取当前类的关联对象属性
NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];

//将全部的强引用对象封装成FBObjectiveCGraphElement
for (id<FBObjectReference> ref in strongIvars) {
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"]) {
/**
If we are dealing with toll-free bridged collections, we are not guaranteed that the collection
will hold only Objective-C objects. We are not able to check in runtime what callbacks it uses to
retain/release (if any) and we could easily crash here.
*/
return [NSSet setWithArray:retainedObjects];
}

if (class_isMetaClass(aCls)) {
// If it's a meta-class it can conform to following protocols,
// but it would crash when trying enumerating
return nil;
}

//获取集合类的引用
if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {
BOOL retainsKeys = [self _objectRetainsEnumerableKeys];
BOOL retainsValues = [self _objectRetainsEnumerableValues];

BOOL isKeyValued = NO;
if ([aCls instancesRespondToSelector:@selector(objectForKey:)]) {
isKeyValued = YES;
}

/**
This codepath is prone to errors. When you enumerate a collection that can be mutated while enumeration
we fall into risk of crash. To save ourselves from that we will catch such exception and try again.
We should not try this endlessly, so at some point we will simply give up.
*/
NSInteger tries = 10;
for (NSInteger i = 0; i < tries; ++i) {
// If collection is mutated we want to rollback and try again - let's keep refs in temporary set
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) {
// mutation happened, we want to try enumerating again
continue;
}

// If we are here it means no exception happened and we want to break outer loop
[retainedObjects addObjectsFromArray:[temporaryRetainedObjects allObjects]];
break;
}
}

return [NSSet setWithArray:retainedObjects];
}

这里最关键的就是FBGetObjectStrongReferences方法:它能从类中获取它的所有引用,无论是强引用或者是弱引用。

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[currentClass] = ivars;
}
}
[array addObjectsFromArray:ivars];
previousClass = currentClass;
currentClass = class_getSuperclass(currentClass);
}

return [array copy];
}

在FBGetObjectStrongReferences方法中遍历本类以及所有父指针强引用,并且加入了缓存以加速查找强引用的过程,在这里会对所有遍历的类调用FBGetStrongReferencesForClass获取ivars,同时过滤弱引用。

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;
return wrapper.type != FBUnknownType;
}
return YES;
}]];

const uint8_t *fullLayout = class_getIvarLayout(aCls);

if (!fullLayout) {
return @[];
}

NSUInteger minimumIndex = FBGetMinimumIvarIndex(aCls);
NSIndexSet *parsedLayout = FBGetLayoutAsIndexesForDescription(minimumIndex, fullLayout);

NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];

return filteredIvars;
}

我们来看下FBGetClassReferences方法:

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];
}

这里主要通过class_copyIvarList方法获取到Ivar然后将所有的Ivar封装到FBIvarReference对象。FBIvarReference对象其实是Ivar的面向对象封装,包括属性的名称、类型、偏移量以及索引。

@interface FBIvarReference : NSObject <FBObjectReference>

@property (nonatomic, copy, readonly, nullable) NSString *name;
@property (nonatomic, readonly) FBType type;
@property (nonatomic, readonly) ptrdiff_t offset;
@property (nonatomic, readonly) NSUInteger index;
@property (nonatomic, readonly, nonnull) Ivar ivar;

- (nonnull instancetype)initWithIvar:(nonnull Ivar)ivar;

@end

到目前为止我们直到了如何获取到某个对象的全部属性,这里包括强引用和弱引用,下面我们还要知道如何过滤弱引用。

为了弄明白怎么过滤弱引用,我们要先了解Ivar Layout,获取Ivar Layout 是通过 FBGetLayoutAsIndexesForDescription方法获取的。
Ivar Layout 是一系列的字符,每两个一组,比如\xmn,每一组 Ivar Layout 中第一位表示有m个非强属性,第二位表示接下来有n个强属性。
FBGetLayoutAsIndexesForDescription 返回的就是所有强引用的Ivar Layout。

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;
}

接下来我们就可以拿着上面找到的parsedLayout对ivars进行过滤,留下所有强引用的ivars。

NSArray<id<FBObjectReference>> *filteredIvars =
[ivars filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id<FBObjectReference> evaluatedObject,
NSDictionary *bindings) {
return [parsedLayout containsIndex:[evaluatedObject indexInIvarLayout]];
}]];

FBObjectiveCBlock

我们来看下Block对象对应的图节点FBObjectiveCBlock:

- (NSSet *)allRetainedObjects {

//获取关联对象的全部强引用
NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];

// Grab a strong reference to the object, otherwise it can crash while doing
// nasty stuff on deallocation
__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方法:

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];
}

在FBGetBlockStrongReferences方法中通过_GetBlockStrongLayout获得Block所持有的强引用。在理解如何Block强引用之前我们先来回顾下Block的一些基础知识:

struct BlockLiteral {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, …);
struct BlockDescriptor *descriptor;
// imported variables
};

首先Block对于它所捕获的Objective-C对象实例,都会在block 结构体的下面存放这些持有的对象,并且会将强引用的对象排放在弱引用对象的前面。当Block将要释放时,会调用dispose_helper函数,该函数会调用所有需要进行内存管理的所捕获的对象,如Block、block变量、__attribute((NSObject))变量或有constructor/destructor的C++ const对象。所以我们可以创建一系列的fake对象来模拟捕获的Objective-C对象实例,然后主动调用Block的dispose_helper方法,该方法会调用对象实例的release方法,我们只需要在fake对象中实现release方法,如果release方法被调用,所以该fake对象对应的真实变量为Objective-C对象实例。

static NSIndexSet *_GetBlockStrongLayout(void *block) {

struct BlockLiteral *blockLiteral = block;

/**
BLOCK_HAS_CTOR - Block has a C++ constructor/destructor, which gives us a good chance it retains
objects that are not pointer aligned, so omit them.

!BLOCK_HAS_COPY_DISPOSE - Block doesn't have a dispose function, so it does not retain objects and
we are not able to blackbox it.
*/
if ((blockLiteral->flags & BLOCK_HAS_CTOR)
|| !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
return nil;
}

//获取当前Block的dispose_helper方法
void (*dispose_helper)(void *src) = blockLiteral->descriptor->dispose_helper;

//指针大小
const size_t ptrSize = sizeof(void *);

//计算出需要填充的fake对象数量
// Figure out the number of pointers it takes to fill out the object, rounding up.
const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;

// 创建fack对象
// Create a fake object of the appropriate length.
void *obj[elements];
void *detectors[elements];

for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}

//调用dispose_helper,这时候会调用每个FBBlockStrongRelationDetector的release方法,这时候会将strong设置为YES
@autoreleasepool {
dispose_helper(obj);
}
// Run through the release detectors and add each one that got released to the object's
// strong ivar layout.
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
//判断strong是否为YES,如果为YES表示为强引用,则将其添加到layout
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) {
[layout addIndex:i];
}
// Destroy detectors
[detector trueRelease];
}
return layout;
}

FBObjectiveCNSCFTimer*

FBObjectiveCNSCFTimer比较简单它会通过runloop去获取CFRunLoopTimerGetContext,然后通过context查看是否有target或者,userInfo如果有假设它强持有这些对象的引用。

- (NSSet *)allRetainedObjects {
// Let's retain our timer
__attribute__((objc_precise_lifetime)) NSTimer *timer = self.object;

if (!timer) {
return nil;
}
NSMutableSet *retained = [[super allRetainedObjects] mutableCopy];
CFRunLoopTimerContext context;
//通过runloop去获取CFRunLoopTimerGetContext
CFRunLoopTimerGetContext((CFRunLoopTimerRef)timer, &context);
// If it has a retain function, let's assume it retains strongly
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;
}

FBAllocationTracker

FBAllocationTracker我们上面给大家分析了FBRetainCycleDetector,它主要的功能是查找到待检测对象的全部强引用对象,这些强引用对象便是搜索循环引用的有向图节点, FBRetainCycleDetector通过深度优先原则遍历整个对象图,如果在整个路径中发现有存在重复访问的情况,便认为是一个循环引用,FBRetainCycleDetector一般先通过MLeaksFinder或者FBAllocationTracker这些开源库找到可疑的对象,然后再通过FBRetainCycleDetector对这个对象进行检查是否存在循环引用。
接下来我们给大家介绍下FBAllocationTracker,下面是它的最基本用法:

#import <FBAllocationTracker/FBAllocationTrackerManager.h>

int main(int argc, char * argv[]) {
[[FBAllocationTrackerManager sharedManager] startTrackingAllocations];
[[FBAllocationTrackerManager sharedManager] enableGenerations];
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- (void)startTrackingAllocations {
FB::AllocationTracker::beginTracking();
}

- (void)enableGenerations {
dispatch_sync(_queue, ^{
if (self->_generationsClients == 0) {
FB::AllocationTracker::enableGenerations();
FB::AllocationTracker::markGeneration();
}
self->_generationsClients += 1;
});
}
Contents
  1. 1. 开篇叨叨
  • FBRetainCycleDetector
    1. 1. 项目结构
    2. 2. 源码解析
  • FBAllocationTracker