1. 使用方式
FBRetainCycleDetector 用于检测循环引用,支持block、timer、associateObj等使用造成的循环引用问题。
主类: FBRetainCycleDetector。
使用姿势如下:
- (void)detectRetainCycle
{
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self.anobj];
[detector addCandidate:self.associatedObj];
[detector addCandidate:self.timer];
NSSet *retainCycles = [detector findRetainCycles];
NSLog(@"retainCycles : %@", retainCycles);
}
/*
timer与viewcontroller的循环引用问题:
2020-11-01 17:30:39.329350+0800 test[31600:984187] retainCycles : {(
(
"-> ViewController ",
"-> _timer -> __NSCFTimer "
)
)}
*/
2. FBRetainCycleDetector介绍
FBRetainCycleDetector(以下简称FB)是一款借助runtime分析发现循环引用的库。
FB将检测循环引用问题,转化为图的深度优先遍历。在遍历过程中检测路径上是否有环:如果有环,说明发生了循环引用;反之,则没有。
检测的关键,在于获取obj(待检测根对象)的所有强引用分支。
1. 首先,获取obj所有强引用对象
1. 获取class中声明的强引用
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) {
// 获取所有强引用的ivar
ivars = FBGetStrongReferencesForClass(currentClass);
if (layoutCache && currentClass) { // 进行缓存
layoutCache[currentClass] = ivars;
}
}
[array addObjectsFromArray:ivars];
previousClass = currentClass;
currentClass = class_getSuperclass(currentClass);
}
return [array copy];
}
2. 获取与obj绑定的所有强引用
需要先hook系统的objc_setAssociatedObject方法;然后,通过associationsForObject获取obj的所有强引用对象。
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
[FBAssociationManager hook];
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
+ (NSArray *)associationsForObject:(id)object
{
#if _INTERNAL_RCD_ENABLED
return FB::AssociationManager::associations(object);
#else
return nil;
#endif //_INTERNAL_RCD_ENABLED
}
3. 如果obj为timer,获取timer的强引用
NSMutableSet *retained = [[super allRetainedObjects] mutableCopy];
CFRunLoopTimerContext context;
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];
}
}
}
4. 如果obj为block,获取obj的强引用
获取block强引用对象在block中偏移量
static NSIndexSet *_GetBlockStrongLayout(void *block)
{
struct BlockLiteral *blockLiteral = block;
/**
如果block有c++的构造/析构函数;或block没有dispose函数就直接返回nil
*/
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 *);
// 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;
// 伪造了一个和block同样布局的obj
void *obj[elements];
void *detectors[elements];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
obj[i] = detectors[i] = detector;
}
// 对obj调用dispose函数,查看是哪个detector会调用release方法
@autoreleasepool {
dispose_helper(obj);
}
//
NSMutableIndexSet *layout = [NSMutableIndexSet indexSet];
for (size_t i = 0; i < elements; ++i) {
FBBlockStrongRelationDetector *detector = (FBBlockStrongRelationDetector *)(detectors[i]);
if (detector.isStrong) { // 找到调用release方法的detector是第几个,也就是真实block中强引用的对象的偏移量
[layout addIndex:i];
}
// Destroy detectors
[detector trueRelease];
}
return layout;
}
2. DFS循环检测
// graphElement: 当前节点
// stackDepth:检测最长步数
- (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];
// Let's start with the root
[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
[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;
}
网友评论