简介

循环引证是常见的导致内存走漏的办法,很简单导致循环引证而且难以发现。FBRetainCycleDetector的作用是在运转时找到引证环。

APM - iOS 内存泄漏监控 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 "
)

原理

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

在没有成环的情况下,从一个节点动身,引证联系是多叉树,所以成环的检测需求对树做遍历。当遍历到的节点,在之前遍历过的节点呈现过,那么该节点处现已成环,记载这条环。

数据结构和算法

调集

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

因为需求O(1)级的判别是否已存在,运用调集来存储现已遍历过的目标

树的遍历

树的遍历有两种办法

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

  • DFS(深度优先查找)

    • 前序遍历

    • 中序遍历

    • 后序遍历

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

  • 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 *转换过来

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

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 是怎么持有目标的