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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 轉至元數據結尾創建: 董瀟偉,最新修改于: 十二月 23, 2016 轉至元數據起始第一章:isa和Class一....
    40c0490e5268閱讀 1,762評論 0 9
  • 把網上的一些結合自己面試時遇到的面試題總結了一下,以后有新的還會再加進來。 1. OC 的理解與特性 OC 作為一...
    AlaricMurray閱讀 2,592評論 0 20
  • 日暮堂前花蕊嬌, 爭拈小筆上床描。 繡成安向青園里, 引得黃鶯下柳條。
    吉吉草閱讀 266評論 0 0
  • 喵是只貓閱讀 156評論 0 0
  • 想象著無數次與你相見 歲月變遷已經不見了曾經的容顏 我站在你身旁 只是多看了你一眼 你回眸時只是不經意間瞥見我的臉...
    田萍閱讀 126評論 0 7