简介
循环引用是常见的导致内存泄漏的方式,很容易导致循环引用而且难以发现。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中通过class_copyIvarList获取所有实例对象Ivars ivar_getTypeEncoding获取ivar的类型,进行处理和过滤 获取到ivars并非都是强引用关系,通过memory layout特征判断强引用 使用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初始化的时候,通过判断类型编码获取到对应类型
由于Swift中类使用ivar_getTypeEncoding无法获取到对应的TypeEncoding,所以最终会是FBUnknownType类型,导致被过滤掉不处理。这也是当前该库不支持Swift项目的原因。
- 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);
}
}