FBRetainCycleDetector

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

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

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

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

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

这里还是借花献福,@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

这里附上网上一位作者(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容

  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,635评论 0 9
  • 把网上的一些结合自己面试时遇到的面试题总结了一下,以后有新的还会再加进来。 1. OC 的理解与特性 OC 作为一...
    AlaricMurray阅读 2,471评论 0 20
  • 日暮堂前花蕊娇, 争拈小笔上床描。 绣成安向青园里, 引得黄莺下柳条。
    吉吉草阅读 165评论 0 0
  • 喵是只猫阅读 134评论 0 0
  • 想象着无数次与你相见 岁月变迁已经不见了曾经的容颜 我站在你身旁 只是多看了你一眼 你回眸时只是不经意间瞥见我的脸...
    田萍阅读 109评论 0 7