APM - iOS 内存泄漏监控 FBRetainCycleDetector代码解析

1,939 阅读10分钟

简介

循环引用是常见的导致内存泄漏的方式,很容易导致循环引用而且难以发现。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);
    }
  }

引用

Automatic memory leak detection on iOS

iOS 中的 block 是如何持有对象的