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对象的 descriptor指针 ,descriptor记录了block对象释放的时候要执行的 dispose_helper 方法和block对象所有引用对象的数组,
这个数组包括了强引用对象和弱应用对象 *src。 也就是说,block被释放时,执行的 dispose_helper 方法的入参 是 *scr;那么只需要伪装一个被引用的数组,传入dispose_helper 做个测试,数组中哪一个对象呗调用了 release 方法,那么谁就是被强引用的,记住src对应下标的地址就好。
查找代码如下:
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引用描述对象
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;
}
//传入伪造的引用的指针数组,执行析构函数,看数组中的哪些对象会被执行release方法,执行的结果在detectors数组中会被记录
@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];
}
// Destroy detectors
[detector trueRelease];
}
return layout;
}
。算法操作草图如下图(手稿图。。。。尴尬。。。):检测核心代码:
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;