FBRetainCycleDetector 使用介绍

5,994 阅读11分钟
原文链接: www.jianshu.com

FBRetainCycleDetector

1. 简单使用

FBRetainCycleDetector用以检测循环引用,可以检测NSObject的循环引用、关联对象(Associated Object)的循环引用、block的循环引用。

#import "ViewController.h"
#import <FBRetainCycleDetector/FBRetainCycleDetector.h>

@interface ViewController ()

@property (nonatomic, strong) NSTimer *timer;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self blockRetainCycle];
    [self timerRetainCycle];
    
    [self testRetainCycle];
}

- (void)blockRetainCycle {
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        NSLog(@"%@", self);
    });
}

- (void)timerRetainCycle {
    _timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"timer");
    }];
}

- (void)testRetainCycle {
    FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] init];
    [detector addCandidate:self];
    NSSet *retainCycles = [detector findRetainCycles];
    NSLog(@"%@", retainCycles);
}

@end

2. 检测循环引用的基本原理和过程

基本的结构图

FBRetainCycleDetector-Structure.png
FBRetainCycleDetector-Structure.png

2.1 findRetainCycles的调用栈

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCycles
└── - (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length
    └── - (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement stackDepth:(NSUInteger)stackDepth
        └── - (instancetype)initWithObject:(FBObjectiveCGraphElement *)object
            └── - (FBNodeEnumerator *)nextObject
                ├── - (NSArray<FBObjectiveCGraphElement *> *)_unwrapCycle:(NSArray<FBNodeEnumerator *> *)cycle
                ├── - (NSArray<FBObjectiveCGraphElement *> *)_shiftToUnifiedCycle:(NSArray<FBObjectiveCGraphElement *> *)array
                └── - (void)addObject:(ObjectType)anObject;

2.2 findRetainCycles的实现

2.2.1 第一个函数

- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCycles {
    return [self findRetainCyclesWithMaxCycleLength:kFBRetainCycleDetectorDefaultStackDepth];
}
  1. 调用第二个函数,传入默认深度kFBRetainCycleDetectorDefaultStackDepth(10)。
- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)findRetainCyclesWithMaxCycleLength:(NSUInteger)length {
    NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *allRetainCycles = [NSMutableSet new];
    for (FBObjectiveCGraphElement *graphElement in _candidates) {
        NSSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [self _findRetainCyclesInObject:graphElement
                                                                                          stackDepth:length];
        [allRetainCycles unionSet:retainCycles];
    }
    [_candidates removeAllObjects];
    
    return allRetainCycles;
}
  1. 建立一个空的set,即allRetainCycles。
  2. 第二个函数主要从_candidates中遍历出element,调用第三个函数,寻找这个element的保留环,将找到的set合并到allRetainCycles中。
- (NSSet<NSArray<FBObjectiveCGraphElement *> *> *)_findRetainCyclesInObject:(FBObjectiveCGraphElement *)graphElement
                                                                 stackDepth:(NSUInteger)stackDepth
{
  NSMutableSet<NSArray<FBObjectiveCGraphElement *> *> *retainCycles = [NSMutableSet new];
  FBNodeEnumerator *wrappedObject = [[FBNodeEnumerator alloc] initWithObject:graphElement];

  // 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];

  ...
}
  1. 有两个数据结构,分别是FBObjectiveCGraphElement和FBNodeEnumerator。显然FBObjectiveCGraphElement的意思是节点edge,FBNodeEnumerator是枚举,基类是NSEnumerator,即迭代器,苹果官方文档说这是一个collection,可以存放object,例如array,dictionary。
  2. 有四个对象,分别是retainCycles用于存放保留环的集合;wrappedObject图的根起点;stack是在图中当前的路径;objectsOnPath是记录以前访问过的节点。
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];
      [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) {
        // Current node still has some adjacent not-visited nodes

        BOOL shouldPushToStack = NO;

        // Check if child was already seen in that path
        if ([objectsOnPath containsObject:firstAdjacent]) {
          // We have caught a retain cycle

          // Ignore the first element which is equal to firstAdjacent, use firstAdjacent
          // we're doing that because firstAdjacent has set all contexts, while its
          // first occurence could be a root without any context
          NSUInteger index = [stack indexOfObject:firstAdjacent];
          NSInteger length = [stack count] - index;

          if (index == NSNotFound) {
            // Object got deallocated between checking if it exists and grabbing its index
            shouldPushToStack = YES;
          } else {
            NSRange cycleRange = NSMakeRange(index, length);
            NSMutableArray<FBNodeEnumerator *> *cycle = [[stack subarrayWithRange:cycleRange] mutableCopy];
            [cycle replaceObjectAtIndex:0 withObject:firstAdjacent];

            // 1. Unwrap the cycle
            // 2. Shift to lowest address (if we omit that, and the cycle is created by same class,
            //    we might have duplicates)
            // 3. Shift by class (lexicographically)

            [retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];
          }
        } else {
          // Node is clear to check, add it to stack and continue
          shouldPushToStack = YES;
        }

        if (shouldPushToStack) {
          if ([stack count] < stackDepth) {
            [stack addObject:firstAdjacent];
          }
        }
      } else {
        // Node has no more adjacent nodes, it itself is done, move on
        [stack removeLastObject];
        [objectsOnPath removeObject:top];
      }
    }
  }
  1. 首先这是一个DFS的过程。DFS是深度优先遍历,我就不介绍了,这里有解释。
  2. 我们来看看这里的代码,我们取出stack的lastObject,命名为top,也就是我们刚刚传入对象的包装,把这个top加入objectsOnPath。
  3. 取top节点的next节点,也就是这个object可能持有的对象。
  4. 节点如果不存在,那么从stack弹出。如果存在,走第5步。
  5. 检查这个firstAdj对象是否存在objectsOnPath中。如果不存在,push到stack中,如果存在,走到第6步。
  6. 计算出firstAdj出现的位置,同时计算出路径的长度,将这一系列的节点(object),也就是环,存放在array里面。将这个array存放到retainCycles集合中。

3. 检测涉及NSObject对象的循环引用问题

3.1 nextObject函数

- (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;
}
  1. nextObject函数是刚刚DFS中比较核心的函数,在nextObject函数中,最为核心的函数就是allRetainedObjects,也就是这个enumerator对应的element,他所持有的对象。获取了所有的对象。
  2. 对于_enumerator的nextObject方还不是很理解???

3.2 allRetainedObjects函数

3.2.1 第一段部分

- (NSSet *)allRetainedObjects
{
  Class aCls = object_getClass(self.object);
  if (!self.object || !aCls) {
    return nil;
  }

  NSArray *strongIvars = FBGetObjectStrongReferences(self.object, self.configuration.layoutCache);

  NSMutableArray *retainedObjects = [[[super allRetainedObjects] allObjects] mutableCopy];

  for (id<FBObjectReference> ref in strongIvars) {
    id referencedObject = [ref objectReferenceFromObject:self.object];

    if (referencedObject) {
      NSArray<NSString *> *namePath = [ref namePath];
      FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
                                                                              referencedObject,
                                                                              self.configuration,
                                                                              namePath);
      if (element) {
        [retainedObjects addObject:element];
      }
    }
  }
  
  ...
  
}

第一段函数主要做了三个操作:

  1. 判断对象或者对象的类是不是存在
  2. 通过FBGetObjectStrongReferences函数获取强引用。
  3. 获取父类的retainObjects,对子类的ivar进行包装,加入retainObjects中。

3.2.2 第二段

- (NSSet *)allRetainedObjects {

    ...

  if ([NSStringFromClass(aCls) hasPrefix:@"__NSCF"]) {
    /**
     If we are dealing with toll-free bridged collections, we are not guaranteed that the collection
     will hold only Objective-C objects. We are not able to check in runtime what callbacks it uses to
     retain/release (if any) and we could easily crash here.
     */
    return [NSSet setWithArray:retainedObjects];
  }

  if (class_isMetaClass(aCls)) {
    // If it's a meta-class it can conform to following protocols,
    // but it would crash when trying enumerating
    return nil;
  }

  if ([aCls conformsToProtocol:@protocol(NSFastEnumeration)]) {
    BOOL retainsKeys = [self _objectRetainsEnumerableKeys];
    BOOL retainsValues = [self _objectRetainsEnumerableValues];

    BOOL isKeyValued = NO;
    if ([aCls instancesRespondToSelector:@selector(objectForKey:)]) {
      isKeyValued = YES;
    }

    /**
     This codepath is prone to errors. When you enumerate a collection that can be mutated while enumeration
     we fall into risk of crash. To save ourselves from that we will catch such exception and try again.
     We should not try this endlessly, so at some point we will simply give up.
     */
    NSInteger tries = 10;
    for (NSInteger i = 0; i < tries; ++i) {
      // If collection is mutated we want to rollback and try again - let's keep refs in temporary set
      NSMutableSet *temporaryRetainedObjects = [NSMutableSet new];
      @try {
        for (id subobject in self.object) {
          if (retainsKeys) {
            FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, subobject, self.configuration);
            if (element) {
              [temporaryRetainedObjects addObject:element];
            }
          }
          if (isKeyValued && retainsValues) {
            FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self,
                                                                         [self.object objectForKey:subobject],
                                                                         self.configuration);
            if (element) {
              [temporaryRetainedObjects addObject:element];
            }
          }
        }
      }
      @catch (NSException *exception) {
        // mutation happened, we want to try enumerating again
        continue;
      }

      // If we are here it means no exception happened and we want to break outer loop
      [retainedObjects addObjectsFromArray:[temporaryRetainedObjects allObjects]];
      break;
    }
  }

  return [NSSet setWithArray:retainedObjects];
  1. 第二段主要实现的功能是
  2. 过滤掉CoreFoundation的集合类和元类,虽然他们遵守NSFastEnumeration协议,但是对他们作处理。(可能防止crash)
  3. 遍历集合,防止mutable发生变化,再遍历一次,确保所有元素获得到了,这里不是很理解。
  4. 捕获异常,continue。

3.3 FBGetObjectStrongReferences函数

NSArray<id<FBObjectReference>> *FBGetObjectStrongReferences(id obj,
                                                            NSMutableDictionary<Class, NSArray<id<FBObjectReference>> *> *layoutCache) {
  NSMutableArray<id<FBObjectReference>> *array = [NSMutableArray new];

  __unsafe_unretained Class previousClass = nil;
  __unsafe_unretained Class currentClass = object_getClass(obj);

  while (previousClass != currentClass) {
    NSArray<id<FBObjectReference>> *ivars;
    
    if (layoutCache && currentClass) {
      ivars = layoutCache[currentClass];
    }
    
    if (!ivars) {
      ivars = FBGetStrongReferencesForClass(currentClass);
      if (layoutCache && currentClass) {
        layoutCache[(id<NSCopying>)currentClass] = ivars;
      }
    }
    [array addObjectsFromArray:ivars];

    previousClass = currentClass;
    currentClass = class_getSuperclass(currentClass);
  }

  return [array copy];
}
  1. 递归查找所有父指针强引用
  2. 使用缓存策略,优先查询当前class是否在缓存中存在,如果存在,加入array。如果不存在,走第3步。
  3. 通过FBGetStrongReferencesForClass函数获取ivars,存入缓存。

3.4 FBGetStrongReferencesForClass函数

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;
}
  1. 我们看函数名字就知道这个函数的功能,获取强引用。
  2. 首先通过FBGetClassReferences函数获取class的引用(包含强引用和弱引用),同时将他们Type置为FBUnknownType。
  3. 通过runtime的class_getIvarLayout获取ivarLayout(ivar在内存中的布局)。
  4. 通过FBGetMinimumIvarIndex获取index,FBGetLayoutAsIndexesForDescription获取强引用的布局信息。
  5. 过滤弱引用。

3.4.1 ivarLayout的介绍

这里我们需要介绍一下ivarLayout,就是指ivar在内存中的布局,sunnyxx的博客有过介绍(我觉得博客中的第二个例子笔误了一点点),其中比较核心的思想是数property,数到strong的property停止,记录一次。

ivarLayout1.png
ivarLayout1.png

这里我们有一个strong,然后一个weak,然后21个strong(最后一个是timer)。 他的布局时什么呢?

ivarLayout2.png
ivarLayout2.png

答案是\x01\x1f\x06。首先是0个weak,一个strong,故01;下面是1个weak,15个strong,故1f;最后是0个weak,6个strong,故06。

3.5 FBGetMinimumIvarIndex函数

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;
}
  1. 见名知义,这个函数目的是为了获取ivar的最小index
  2. 这里使用了runtime的class_copyIvarList函数获取ivars。
  3. 获取ivars[0]的偏移量,minimumIndex = offset / (sizeof(void *));中sizeof(void *)应该是由编译器的指令集决定,x86就是4,x64是8。所以这里应该就是获取到第一个ivar的index。

3.6 FBGetLayoutAsIndexesForDescription函数

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;
}
  1. 这个函数是获取强引用的index区间。
  2. C语言学的不好,我大概理解为,upper是非strong偏移量,lower是strong偏移量。最后记录非strong结束到strong个数。

4. 检测Block的循环引用问题

4.1 几个重要的概念介绍

4.1.1 block基本知识介绍

Objective-C 中的三种 block __NSMallocBlock____NSStackBlock____NSGlobalBlock__ 会在下面的情况下出现:
这里参考的博客

image.png
image.png

4.1.2 blockInterface中的数据结构

struct BlockLiteral {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct BlockDescriptor *descriptor;
};

struct BlockDescriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy_helper)(void *dst, void *src);
    void (*dispose_helper)(void *src);
    const char *signature;
};

这里两个结构体其实是模仿LLVM中block的实现。这样可以使用我们自己的block替换系统的block,做一个伪造的block。

4.1.3 block存储强弱引用的顺序

block-strong-weak-order.png
block-strong-weak-order.png

这里还是借花献福,@Draveness做了一个实验,将强弱引用存储在block中,最后按照block持有的参数按序输出,得到结果就是强应用在前,若引用在后。现在我们就是要找到强弱引用的分界线。另外block的指针占用8个字节,block本身占用32个字节。

4.2 allRetainedObjects函数

- (NSSet *)allRetainedObjects
{
  NSMutableArray *results = [[[super allRetainedObjects] allObjects] mutableCopy];

  // Grab a strong reference to the object, otherwise it can crash while doing
  // nasty stuff on deallocation
  __attribute__((objc_precise_lifetime)) id anObject = self.object;

  void *blockObjectReference = (__bridge void *)anObject;
  NSArray *allRetainedReferences = FBGetBlockStrongReferences(blockObjectReference);

  for (id object in allRetainedReferences) {
    FBObjectiveCGraphElement *element = FBWrapObjectGraphElement(self, object, self.configuration);
    if (element) {
      [results addObject:element];
    }
  }

  return [NSSet setWithArray:results];
}
  1. 我们还是从allRetainedObjects函数开始看起。
  2. 这里第一步没怎么看懂,但是注释的意思是强引用object,否则在dealloc的时候会crash。
  3. 这个函数的核心还是调用FBGetBlockStrongReferences函数,获取block强引用。
  4. 包装获取到的强引用成element,返回。

4.3 FBGetBlockStrongReferences函数

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];
}
  1. 首先判断是不是block对象
  2. 然后通过_GetBlockStrongLayout函数获取强引用的layout。
  3. 强引用的layout遍历没看懂

4.4 _GetBlockStrongLayout函数

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;
}
  1. 这是一个核心函数,主要负责取出block中的强引用。
  2. 首先排除掉两种情况:有C++构造和析构的,没有dispose方法的。
  3. 取出函数指针dispose_helper,这个函数应该就是销毁对象的辅助函数。
  4. 伪造出一个对象数组,数组的个数就是block持有对象的个数。
  5. 对伪造的对象进行dispose_helper操作。这时候dispose_helper函数应该会向所持有的对象发送释放信息,我们用一个FBBlockStrongRelationDetector实例对消息进行接受,如果收到消息,将_strong置为YES,这里还将release进行替换了,实现了一个trueRelease。
block-release.png
block-release.png

这里附上网上一位作者(alonemoney)的理解:

这里使用了一个黑盒技术。创建一个对象来假扮想要调查的Block。因为知道Block的接口,知道在哪可以找到Block持有的引用。伪造的对象将会拥有“释放检测(release detectors)”来代替这些引用。释放检测器是一些很小的对象,这里是FBBlockStrongRelationDetector,它们会观察发送给它们的释放消息。当持有者想要放弃它的持有的时候,这些消息会发送给强引用对象。当释放伪造的对象的时候,可以检测哪些检测器接收到了这些消息。只要知道哪些索引在伪造的对象的检测器中,就可以找到原来Block中实际持有的对象。

5. 检测涉及Associated Object关联对象的循环引用问题

最后我们来看看关联对象的循环引用如何处理的

+ (NSArray *)associationsForObject:(id)object {
    return FB::AssociationManager::associations(object);
}

调用下面的associations函数。

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;
  }
  1. 判空处理
  2. 这里_associationMap负责存储所有的关联对象,另一个value里面还进行一次循环,根据key和object取出管理的对象。
  3. return所有的关联对象。

6. 参考文章

我的demo地址:github
Draveness关于FBRetainCycleDetector源码的解析:Draveness
alonemoney关于FBRetainCycleDetector源码的解析:alonemoney
LLVM关于block的源码:llvm