简介
循环引证是常见的导致内存走漏的办法,很简单导致循环引证而且难以发现。FBRetainCycleDetector的作用是在运转时找到引证环。
循环引证为什么会导致内存走漏
ARC(主动引证计数)运用对目标的引证计数来追寻和办理目标的生命周期,当发生循环引证的时分,其间的目标引证计数不会被置为0,所以不会被开释,形成内存走漏
ARC(主动引证计数)和GC(废物收集器)的区别
-
ARC是实时运转的,ARC在目标不再运用的时分,能够当即开释,通常在runloop的事件中,autoreleasepool会履行drain(清空)的操作
-
GC是阶段性的,需求花些时刻定位和开释不再运用的目标
运用
创立FBRetainCycleDetector的目标,增加需求进行循环引证检测的根节点。把未开释的目标,此处是未开释的ViewController,所以FBRetainCycleDetector根MLeaksFinder这一类的工具很搭配。
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self.object];
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];
取得的引证链信息如下
(
"-> target -> ChildViewController ",
"-> _timer -> __NSCFTimer "
)
原理
在没有成环的情况下,从一个节点动身,引证联系是多叉树,所以成环的检测需求对树做遍历。当遍历到的节点,在之前遍历过的节点呈现过,那么该节点处现已成环,记载这条环。
数据结构和算法
调集
因为需求O(1)级的判别是否已存在,运用调集来存储现已遍历过的目标
树的遍历
树的遍历有两种办法
-
DFS(深度优先查找)
-
前序遍历
-
中序遍历
-
后序遍历
-
-
BFS(广度优先查找)
-
层次遍历
-
因为BFS是基于起始点的规模扩散,能够看成是从起始点开端,一步一步往目标点扩散,所以BFS合适解决从A点到B点的最短路径问题。假如只是判别是否成环,那么BFS是最快的遍历办法。
假如需求获取一切的环,那么DFS和BFS就没有区别了,因为需求遍历到每个节点,节点数固定下来,核算的步数就现已确认下来,DFS运用递归办法更简单完成。
前序遍历
递归完成
public class Node {
public var val: Int
public var children: [Node]
public init(_ val: Int) {
self.val = val
self.children = []
}
}
class Solution {
func preorder(_ root: Node?) -> [Int] {
var result = [Int]()
traversal(root, &result)
return result
}
func traversal(_ root: Node?, _ result: inout [Int]) {
if let root = root {
result.append(root.val)
for chid in root.children {
traversal(chid, &result)
}
} else {
return
}
}
}
递归的情况下参数,返回地址,本地变量会占用栈空间,递归深度高的情况下有或许导致栈溢出。从内存办理的视点,会挑选非递归完成。
非递归完成
class Solution {
func preorder(_ root: Node?) -> [Int] {
guard let root = root else { return [Int]() }
var result = [Int]()
var stack = [Node]()
stack.append(root)
while (stack.count > 0) {
let node = stack.removeLast()
result.append(node.val)
for child in node.children.reversed() {
stack.append(child)
}
}
return result
}
}
该库中对非递归遍历的完成,有一些区别
- 栈中根节点,不是用完直接弹出
- 会回溯到根节点,节点会运用屡次,需求记载是否拜访过的状况,防止重复
- 获取根节点的子节点的时分,因为会拜访屡次,运用NSEnumerator记载迭代的状况
class Solution {
func preorder(_ root: Node?) -> [Int] {
guard let root = root else { return [Int]() }
var result = [Int]()
var stack = [Node]()
stack.append(root)
var visitedNode = Set([Node]())
while (stack.count > 0) {
let node = stack.last!
if !visitedNode.contains(node) {
result.append(node.val)
}
visitedNode.append(node)
var childrenIterator = node.children.makeIterator()
var child = childrenIterator.next()
if let child = child {
stack.append(child)
} else {
stack.removeLast()
}
}
return result
}
}
剪枝
对于部分不符合条件的节点不再持续遍历下去,所以需求一个filter来过滤剪枝
检测流程
运用宏作为功用开关
#define MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED 0
#ifdef MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED
#define _INTERNAL_MLF_RC_ENABLED MEMORY_LEAKS_FINDER_RETAIN_CYCLE_ENABLED
#elif COCOAPODS
#define _INTERNAL_MLF_RC_ENABLED COCOAPODS
#endif
增加根节点,开端检测
FBRetainCycleDetector *detector = [FBRetainCycleDetector new];
[detector addCandidate:self.object];
NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:20];
把根节点增加进来之前,运用FBObjectiveCGraphElement包装
- (void)addCandidate:(id)candidate
{
FBObjectiveCGraphElement *graphElement = FBWrapObjectGraphElement(nil, candidate, _configuration);
if (graphElement) {
[_candidates addObject:graphElement];
}
}
能够经过FBObjectGraphConfiguration类进行装备过滤和检测功用选项
FBObjectiveCGraphElement *FBWrapObjectGraphElement(FBObjectiveCGraphElement *sourceElement,
id object,
FBObjectGraphConfiguration *configuration) {
return FBWrapObjectGraphElementWithContext(sourceElement, object, configuration, nil);
}
实际包装过程中,区别增加进来的目标类型,运用FBObjectiveCGraphElement的3个不同的子类结构包装
FBObjectiveCGraphElement的不同子类结构获取对应强引证联系的完成不同
- FBObjectiveCGraphElement
- FBObjectiveCBlock
- FBObjectiveCObject
- FBObjectiveCNSCFTimer
FBObjectiveCGraphElement *FBWrapObjectGraphElementWithContext(FBObjectiveCGraphElement *sourceElement,
id object,
FBObjectGraphConfiguration *configuration,
NSArray<NSString *> *namePath) {
if (_ShouldBreakGraphEdge(configuration, sourceElement, [namePath firstObject], object_getClass(object))) {
return nil;
}
FBObjectiveCGraphElement *newElement;
if (FBObjectIsBlock((__bridge void *)object)) {
newElement = [[FBObjectiveCBlock alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
if ([object_getClass(object) isSubclassOfClass:[NSTimer class]] &&
configuration.shouldInspectTimers) {
newElement = [[FBObjectiveCNSCFTimer alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
newElement = [[FBObjectiveCObject alloc] initWithObject:object
configuration:configuration
namePath:namePath];
}
}
return (configuration && configuration.transformerBlock) ? configuration.transformerBlock(newElement) : newElement;
}
运用FBNodeEnumerator包装FBObjectiveCGraphElement
FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];
FBNodeEnumerator主要是迭代器的完成
@interface FBNodeEnumerator : NSEnumerator
/**
Designated initializer
*/
- (nonnull instancetype)initWithObject:(nonnull FBObjectiveCGraphElement *)object;
- (nullable FBNodeEnumerator *)nextObject;
@property (nonatomic, strong, readonly, nonnull) FBObjectiveCGraphElement *object;
@end
运用非递归的办法完成DFS,运用autoreleasepool来控制暂时变量在每次循环遍历后及时开释
// We will be doing DFS over graph of objects
// Stack will keep current path in the graph
NSMutableArray<FBNodeEnumerator *> *stack = [NSMutableArray new];
// To make the search non-linear we will also keep
// a set of previously visited nodes.
NSMutableSet<FBNodeEnumerator *> *objectsOnPath = [NSMutableSet new];
// Let's start with the root
[stack addObject:wrappedObject];
while ([stack count] > 0) {
// Algorithm creates many short-living objects. It can contribute to few
// hundred megabytes memory jumps if not handled correctly, therefore
// we're gonna drain the objects with our autoreleasepool.
@autoreleasepool {
// Take topmost node in stack and mark it as visited
FBNodeEnumerator *top = [stack lastObject];
// We don't want to retraverse the same subtree
if (![objectsOnPath containsObject:top]) {
if ([_objectSet containsObject:@([top.object objectAddress])]) {
[stack removeLastObject];
continue;
}
// Add the object address to the set as an NSNumber to avoid
// unnecessarily retaining the object
[_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
FBNodeEnumerator *firstAdjacent = [top nextObject];
if (firstAdjacent) {
if ([objectsOnPath containsObject:firstAdjacent]) {
// Codes
} else {
[stack addObject:firstAdjacent];
}
} else {
// Node has no more adjacent nodes, it itself is done, move on
[stack removeLastObject];
[objectsOnPath removeObject: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
FBNodeEnumerator *firstAdjacent = [top nextObject];
FBNodeEnumerator内部运用NSEnumerator作为迭代器,会持久化迭代的状况,所以不需求在一次遍历中获取一切的子节点。而是保留了根节点,每次经过迭代器获取一个子节点,进行处理。
- (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;
}
强引证联系
检测中的关键步骤是找到子节点,确认强引证联系,涉及到4种类型的目标。其间NSObject,Timer,Block能够在实例的结构中找到对应的强引证联系,Associate需求在运用的时分hook对应的办法,获取强引证信息
- NSObject
- layout
- Block
- blockLiteral
- Timer
- CFRunLoopTimerContext
- Associate
- fishhook
NSObject
在Objective-C中实例目标Ivars能够经过class_copyIvarList获取,ivars并非都是强引证联系,判别强引证之后运用Predicate进行过滤,得到一切的强引证联系
- class_getIvarLayout获取类的Ivar Layout,(const uint8_t) *fullLayout = ‘\x01’,一个字节,其间上半字节x0代表弱引证数目,下半字节x1代表强引证数目
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 nil;
}
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;
}
运用class_copyIvarList获取类一切的ivars,遍历ivars包装成FBIvarReference的数组返回
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];
}
FBIvarReference初始化的时分,经过判别类型编码获取到对应类型
- FBType
- FBObjectType
- FBBlockType
- FBStructType
@implementation FBIvarReference
- (instancetype)initWithIvar:(Ivar)ivar
{
if (self = [super init]) {
_name = @(ivar_getName(ivar));
_type = [self _convertEncodingToType:ivar_getTypeEncoding(ivar)];
_offset = ivar_getOffset(ivar);
_index = _offset / sizeof(void *);
_ivar = ivar;
}
return self;
}
- (FBType)_convertEncodingToType:(const char *)typeEncoding
{
if (typeEncoding[0] == '{') {
return FBStructType;
}
if (typeEncoding[0] == '@') {
// It's an object or block
// Let's try to determine if it's a block. Blocks tend to have
// @? typeEncoding. Docs state that it's undefined type, so
// we should still verify that ivar with that type is a block
if (strncmp(typeEncoding, "@?", 2) == 0) {
return FBBlockType;
}
return FBObjectType;
}
return FBUnknownType;
}
@end
获取变量最低索引值,获取首个ivar在内存上的廉价,除以类型的size,得到最小索引值
static NSUInteger FBGetMinimumIvarIndex(__unsafe_unretained Class aCls) {
NSUInteger minimumIndex = 1;
unsigned int count;
Ivar *ivars = class_copyIvarList(aCls, &count);
if (count > 0) {
Ivar ivar = ivars[0];
ptrdiff_t offset = ivar_getOffset(ivar);
minimumIndex = offset / (sizeof(void *));
}
free(ivars);
return minimumIndex;
}
获取强引证的方位索引Range的调集,NSMakeRange(currentIndex, lowerNibble)是强引证规模。上半字节高位表示弱引证的数量,下半字节低位表示强引证的数量,把一切currentIndex到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;
// Upper nimble is for skipping
currentIndex += upperNibble;
// Lower nimble describes count
[interestingIndexes addIndexesInRange:NSMakeRange(currentIndex, lowerNibble)];
currentIndex += lowerNibble;
++layoutDescription;
}
return interestingIndexes;
}
Block
block结构
struct BlockLiteral {
void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
int flags;
int reserved;
void (*invoke)(void *, ...);
struct BlockDescriptor *descriptor;
// imported variables
};
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)
};
从_GetBlockStrongLayout办法中取得strongLayout,遍历获取一切强引证目标
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];
}
-
在block类型中疏忽掉了含C++的类型和无dispose函数的类型
- 含c++的结构器和析构器,阐明持有的目标有或许没有依照指针大小对齐
- 不含dispose函数,阐明无法retain目标,咱们也无法测试
-
从Descriptor类型,取出dispose_helper的size,是持有一切目标的size,除以目标的类型得到目标指针的数目
-
初始化obj用于传入dispose_helper,初始化detectors用于符号_strong
-
在主动开释池中履行dispose_helper(obj),开释block持有的目标
-
dispose_helper会调用release办法,但是不会导致FBBlockStrongRelationDetector开释掉,反而会符号_strong特点
-
遍历detectors中detector的_strong特点,增加在strongLayout中
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;
}
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;
// 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;
}
@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];
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;
}
Timer
- 获取Timer目标,获取Timer的context
- 假如有retain函数,咱们假设是强引证
- (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;
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;
}
Timer的context结构
typedef struct {
CFIndex version;
void * info;
const void *(*retain)(const void *info);
void (*release)(const void *info);
CFStringRef (*copyDescription)(const void *info);
} CFRunLoopTimerContext;
Timer的context中info结构_FBNSCFTimerInfoStruct,这个结构体直接从void *转换过来
typedef struct {
long _unknown; // This is always 1
id target;
SEL selector;
NSDictionary *userInfo;
} _FBNSCFTimerInfoStruct;
FBWrapObjectGraphElementWithContext中判别element的类型,返回对应类型的目标,此处转为FBObjectiveCObject
FBObjectiveCGraphElement *FBWrapObjectGraphElementWithContext(FBObjectiveCGraphElement *sourceElement,
id object,
FBObjectGraphConfiguration *configuration,
NSArray<NSString *> *namePath) {
if (_ShouldBreakGraphEdge(configuration, sourceElement, [namePath firstObject], object_getClass(object))) {
return nil;
}
FBObjectiveCGraphElement *newElement;
if (FBObjectIsBlock((__bridge void *)object)) {
newElement = [[FBObjectiveCBlock alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
if ([object_getClass(object) isSubclassOfClass:[NSTimer class]] &&
configuration.shouldInspectTimers) {
newElement = [[FBObjectiveCNSCFTimer alloc] initWithObject:object
configuration:configuration
namePath:namePath];
} else {
newElement = [[FBObjectiveCObject alloc] initWithObject:object
configuration:configuration
namePath:namePath];
}
}
return (configuration && configuration.transformerBlock) ? configuration.transformerBlock(newElement) : newElement;
}
Associate
获取相关目标的强引证数据,运用AssociationManager中associationsForObject办法。数据的发生需求依赖于hook objc_setAssociatedObject记载对应的映射信息。
获取数据
调用AssociationManager中的associations办法
+ (NSArray *)associationsForObject:(id)object
{
#if _INTERNAL_RCD_ENABLED
return FB::AssociationManager::associations(object);
#else
return nil;
#endif //_INTERNAL_RCD_ENABLED
}
_associationMap中记载了某一个目标的一切相关目标的强引证
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) {
id value = objc_getAssociatedObject(object, key);
if (value) {
[array addObject:value];
}
}
return array;
}
发生数据
库作者表示因为条件竞赛的原因,对相关目标的检测未必彻底精确。
运用fishhook对objc_setAssociatedObject进行hook,objc_setAssociatedObject的办法会先走到FB::AssociationManager::fb_objc_setAssociatedObject办法中。
关于fishhook的原理能够查阅我的其他博客。
+ (void)hook
{
#if _INTERNAL_RCD_ENABLED
std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);
rebind_symbols((struct rebinding[2]){
{
"objc_setAssociatedObject",
(void *)FB::AssociationManager::fb_objc_setAssociatedObject,
(void **)&FB::AssociationManager::fb_orig_objc_setAssociatedObject
},
{
"objc_removeAssociatedObjects",
(void *)FB::AssociationManager::fb_objc_removeAssociatedObjects,
(void **)&FB::AssociationManager::fb_orig_objc_removeAssociatedObjects
}}, 2);
FB::AssociationManager::hookTaken = true;
#endif //_INTERNAL_RCD_ENABLED
}
OBJC_ASSOCIATION类型
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object.
* The association is not made atomically. */
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied.
* The association is not made atomically. */
OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object.
* The association is made atomically. */
OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied.
* The association is made atomically. */
};
检测强引证相关目标
static void fb_objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) {
{
std::lock_guard<std::mutex> l(*_associationMutex);
// Track strong references only
if (policy == OBJC_ASSOCIATION_RETAIN ||
policy == OBJC_ASSOCIATION_RETAIN_NONATOMIC) {
_threadUnsafeSetStrongAssociation(object, key, value);
} else {
// We can change the policy, we need to clear out the key
_threadUnsafeResetAssociationAtKey(object, key);
}
}
/**
We are doing that behind the lock. Otherwise it could deadlock.
The reason for that is when objc calls up _object_set_associative_reference, when we nil out
a reference for some object, it will also release this value, which could cause it to dealloc.
This is done inside _object_set_associative_reference without lock. Otherwise it would deadlock,
since the object that is released, could also clean up some associated objects.
If we would keep a lock during that, we would fall for that deadlock.
Unfortunately this also means the association manager can be not a 100% accurate, since there
can technically be a race condition between setting values on the same object and same key from
different threads. (One thread sets value, other nil, we are missing this value)
*/
fb_orig_objc_setAssociatedObject(object, key, value, policy);
}
AssociationManager
- AssociationMap用于存储目标到ObjectAssociationSet指针的映射
- ObjectAssociationSet用于存储某目标一切相关目标的调集
using ObjectAssociationSet = std::unordered_set<void *>;
using AssociationMap = std::unordered_map<id, ObjectAssociationSet *>;
记载目标相关信息
- 以object作为键,查找或创立ObjectAssociationSet的调集,把新的key刺进到调集中
- value为nil的话,会重置ObjectAssociationSet中的相关目标
void _threadUnsafeSetStrongAssociation(id object, void *key, id value) {
if (value) {
auto i = _associationMap->find(object);
ObjectAssociationSet *refs;
if (i != _associationMap->end()) {
refs = i->second;
} else {
refs = new ObjectAssociationSet;
(*_associationMap)[object] = refs;
}
refs->insert(key);
} else {
_threadUnsafeResetAssociationAtKey(object, key);
}
}
引证
Automatic memory leak detection on iOS
iOS 中的 block 是怎么持有目标的