有哪些办法可以监测循环引证?
在引证计数的内存管理办法中,因为目标间的引证,最终引证联系图形成“环”才导致循环引证。因而对循环引证的监测直观的主意只需求找到这个环就可以找到循环引证的当地,也便是在有向图中找环(也可以说在树中找环),一起需求找到环中详细的节点,例如 FBRetainCycleDector 便是选用 DFS 进行图中环的检测与查找。
不过还有另一种办法,便是假定目标会很快开释。例如当一个 vc
被 pop
或 dismiss
之后,应该认为该 vc
包含它上面的子 vc
,以及它的 view
,view
的 subView
等,都会被很快开释,假如某个目标没有被开释,就认为该目标内存泄漏了,例如 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 相关类,如 UIViewController
、UIView
、UINavigationController
、UIPageViewController
等,并拓宽了 NSObject
,为其增加 willDealloc
办法。在 UIViewController
或许 UINavigationController
在调用 dismiss
、pop
时,就会调用 vc
、vc
的子 viewControllers
、vc
的 view
、view
的 subView
的 willDealloc
办法。运用 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;
}
但这样的内存办法存在两种办法的“误判”:
- 单例或许被缓存起来的
view
或vc
- 开释不及时的
view
或许vc
对此,MLeaksFinder 也进行了一系列的措施进行补救:
- 经过 assert 确保调用在主线程,按
vc
、vc
的子viewcontrollers
、vc
的view
这样的顺序调用它们的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
中,假如parentPtrs
和leakedObjectPtrs
有相同的交集,就不会弹窗,直接退出,这也确保了在同一个循环引证中只有一个弹窗的调用。假如目标被销毁(或许是开释不及时的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]];
});
}
单例或许被缓存起来的 view
或 vc
来说,因为 leakedObjectPtrs
留有一份地址,所以当重复进入、退出页面时,不会重复进行弹窗;
关于开释不及时的 view
或许 vc
来说,在未被开释前,会发生弹窗,在开释之后,弹出的信息变为 Object Deallocated
,也便是不只会重复弹窗,并且还有新加弹窗。这是因为关于 vc
和 view
来说,它们的内存往往占用较大,因而应该立即被开释,如网络回调中 block
的强持有,这种情况就应把强引证改为弱引证;
关于真实循环引证的目标,因为每次都会创建新的目标,因而会重复弹窗;
不过 MLeaksFinder 的缺陷也很明显,大部分只能用来对它做 view
、vc
的循环引证监测,关于 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_setAssociatedObject
和 objc_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
办法进行循环引证查找并回来循环引证链。它的中心类图如下:
传入的目标会被封装为 FBObjectiveCGraphElement
类型,一起它有三个子类 FBObjectiveCBlock
、FBObjectiveCObject
与 FBObjectiveCNSCFTimer
。这是因为需求弱引证待检测目标,一起不同目标的内存布局不同(如分为一般 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]
则是循环引证链的相关目标。
- (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;
}
怎么获取目标强引证指针
FBObjectiveCGraphElement
的 allRetainedObjects
办法回来了目标的一切的强引证。上文也提到过,不同的目标有不同的获取完成,这也是可以监测循环引证的关键:
获取 NSObject
的强引证:
获取 NSObject
目标的强引证在 FBObjectiveCObject
类中完成,它运用 Runtime 的一些函数获得了 ivars
和 ivarLayout
(区分了哪些是强引证和弱引证),它的几个中心办法逻辑如下(按调用顺序):
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
:
经过 fullLayout
和 minimumIndex
获取一切强引证的 Range。fullLayout
它以若干组 \xnm
方式表明,n 表明 n 个非强特点,m 表明有 m 个强特点;
minimumIndex
表明开端位,upperNibble
表明非强引证数量,因而需求加上 upperNibble
,NSMakeRange(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 本身的一些特点,如 isa
,flag
,size
等)。
不过因为其指针对齐与捕获变量排序机制(一般按__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
函数,就假定它含有强引证目标,将 target
和 userInfo
分别将其以 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_setAssociatedObject
和 objc_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