facebook / FBRetainCycleDetector
帮助在运行时检测循环引用的iOS 库
FBRetainCycleDetector
使用运行时分析找到循环应用的iOS 库。
关于
循环引用是造成内存泄露最常见的原因。要创建一个循环引用相当简单,并且往往难以发现它。FBRetainCycleDetector 的目标是帮助在运行时找到循环引用。这个项目的功能受到 [Circle]( GitHub - mikeash/Circle ) 的启发。
安装
Carthage
在你的 Cartfile 里添加:
github "facebook/FBRetainCycleDetector"
FBRetainCycleDetector
是从非调适版本构建的,所以当你想测试它的时候,使用
carthage update --configuration Debug
CocoaPods
在你的 podspec 里添加:
pod 'FBRetainCycleDetector'
你只能在 Debug
构建中才能完全使用 FBRetainCycleDetector
。由 [compilation flag]( FBRetainCycleDetector/FBRetainCycleDetector.h at master · facebook/FBRetainCycleDetector · GitHub ),可以提供在其它配置的构建中使用。
使用示例
让我们迅速深入
#import <FBRetainCycleDetector/FBRetainCycleDetector.h>
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"%@", retainCycles);
- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCycles
会返回一组包装对象的数组。刚开始看会很困难,但是可以克服。这个 set 里的每个数组都表示一个循环引用。此数组中的每个元素都是此引用循环中一个对象的包装器。参考 [FBObjectiveCGraphElement]( FBRetainCycleDetector/FBObjectiveCGraphElement.h at master · facebook/FBRetainCycleDetector · GitHub )。
输出示例如下:
{(
(
"-> MyObject ",
"-> _someObject -> __NSArrayI "
)
)}
MyObject
通过 someObject
属性引用 NSArray
同时它也是 NSArray
的一部分。
FBRetainCycleDetector
会查找不超过10个对象的循环。我们可以让它查找更多(虽然会更慢!)。
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:100];
过滤器
也有我们想忽略的循环引用。因为不是每个循环引用都是泄露,所以我们想要过滤掉它们。为此,我们需要指定过滤器:
NSMutableArray *filters = @[
FBFilterBlockWithObjectIvarRelation([UIView class], @"_subviewCache"),
];
// Configuration object can describe filters as well as some options
FBObjectGraphConfiguration *configuration =
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filters
shouldInspectTimers:YES];
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:configuration];
[detector addCandidate:myObject];
NSSet *retainCycles = [detector findRetainCycles];
可以说每个过滤器是一个拥有两个 FBObjectiveCGraphElement
对象的 block,如果它们的关系有效的话。
参考 FBStandardGraphEdgeFilters 来学习更多如何使用过滤器。
NSTimer
NSTimer 可能会很麻烦,因为它会引用它的目标。通常它意味着循环引用。FBRetainCycleDetector
可以检测它们,但如果你想跳过,可以在传给 FBRetainCycleDetector
的配置里指明.
FBObjectGraphConfiguration *configuration =
[[FBObjectGraphConfiguration alloc] initWithFilterBlocks:someFilters
shouldInspectTimers:NO];
FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:configuration];
关联
Objective-C 允许用户使用 objc_setAssociatedObject 为每个对象设置关联对象。
这些关联对象可能会导致循环引用,如果我们使用 retaining policies 的话,例如 OBJC_ASSOCIATION_RETAIN_NONATOMIC
。FBRetainCycleDetector 可以捕获这种类型的循环,但是需要设置一下。在应用声明周期的一开始,最好在 main.m
里我们可以这样添加:
#import <FBRetainCycleDetector/FBAssociationManager.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
[FBAssociationManager hook];
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
上面的代码里 [FBAssociationManager hook]
会在关联被创建前使用 fishhook 来插入 objc_setAssociatedObject
和 objc_resetAssociatedObjects
函数来追踪它们。
获得 Candidates
如果想要对 app 进行性能分析,你可能想要抽象理解 FBRetainCycleDetector
是如何获得 candidates 的。虽然你自己很简单的就可以追踪它们,但你也可以使用 FBAllocationTracker。这是我们创建的一个小工具,可以帮助你追踪对象。它提供了简单的API,您可以查询例如给定类的所有实例,或当前跟踪的所有类名等。
FBAllocationTracker
和 FBRetainCycleDetector
可以很好地合作。 我们创建了一个小型示例和名为 FBMemoryProfiler 的插件项目来利用这两个项目。 它提供了非常基本的UI,您可以使用它从 UI 检测跟踪所有分配和强制引用循环。