天天看點

FBRetainCycleDetector深入解析

FBRetainCycleDetector是什麼

FBRetainCycleDetector

FaceBook

開源的用于檢測強引用循環的工具。預設是在

DEBUG

環境中啟用,當然你也可以通過設定

RETAIN_CYCLE_DETECTOR_ENABLED

以始終開啟。

FBRetainCycleDetector如何使用

FBRetainCycleDetector

的使用非常簡單。建立檢測器,将可疑的對象添加到檢測器中,然後調用開始檢測即可。如果需要經常使用,可以考慮寫一個單例,避免頻繁的建立銷毀。

- (void)retainCycleDetectWithObject:(id)object {
    if (!object) {
        return;
    }

    // 建立檢測器
    FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] init];
    // 添加檢測對象
    [detector addCandidate:object];
    // 開始檢測
    NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:10];
	NSLog(@"%@",retainCycles);
}

           

FBRetainCycleDetector是如何運作的

下面我們以上面的三行代碼為突破口,深入探究一下

FBRetainCycleDetector

到底是如何運作的

  • 建立檢測器
- (instancetype)init
{
  return [self initWithConfiguration:
          [[FBObjectGraphConfiguration alloc] initWithFilterBlocks:FBGetStandardGraphEdgeFilters()
                                               shouldInspectTimers:YES]];
}
           

我們發現它的

init

方法裡面建立了一個配置類

FBObjectGraphConfiguration

。該方法共有兩個參數,一個是元素類型為

FBGraphEdgeFilterBlock

的過濾數組,一個是是否檢測

NSTimer

。這個過濾數組相當于是配置一些規則,告訴檢測器哪些對象不需要檢測。

FBGraphEdgeFilterBlock

的定義如下。

建立

FBGraphEdgeFilterBlock

FBGraphEdgeFilterBlock FBFilterBlockWithObjectToManyIvarsRelation(Class aCls,
                                                                  NSSet<NSString *> *ivarNames) {
  return ^(FBObjectiveCGraphElement *fromObject,
           NSString *byIvar,
           Class toObjectOfClass){
    if (aCls &&
        [[fromObject objectClass] isSubclassOfClass:aCls]) {
      // If graph element holds metadata about an ivar, it will be held in the name path, as early as possible
      if ([ivarNames containsObject:byIvar]) {
        return FBGraphEdgeInvalid;
      }
    }
    return FBGraphEdgeValid;
  };
}
           

FBGraphEdgeFilterBlock

的傳回值為

FBGraphEdgeType

類型,分别是

Valid

Invalid

,相當于一個

BOOL

值,判斷對象有效或者無效。由此可見該Block的作用就是對傳入的對象進行過濾。

了解了這一點,我們就可以建立自己的過濾規則了,舉個例子:

NSMutableArray<FBGraphEdgeFilterBlock> *filterBlocks = FBGetStandardGraphEdgeFilters().mutableCopy;
    // 自定義過濾規則,不檢測UITableView的delegate和dataSourse屬性
    FBGraphEdgeFilterBlock block = FBFilterBlockWithObjectToManyIvarsRelation([UITableView class], [NSSet setWithArray:@[@"_delegate",@"_dataSourse",]]);
    [filterBlocks addObject:block];
    [[FBObjectGraphConfiguration alloc] initWithFilterBlocks:filterBlocks.copy shouldInspectTimers:YES];

    // 建立檢測器
    FBRetainCycleDetector *detector = [[FBRetainCycleDetector alloc] initWithConfiguration:config];
    // 添加檢測對象
    [detector addCandidate:object];
    // 開始檢測
    NSSet *retainCycles = [detector findRetainCyclesWithMaxCycleLength:10];
           

這裡設定的過濾

Block

将在下一步添加檢測對象的時候調用。

  • 添加檢測對象
- (void)addCandidate:(id)candidate
{
    FBObjectiveCGraphElement *graphElement = FBWrapObjectGraphElement(nil, candidate, _configuration);
    if (graphElement) {
        [_candidates addObject:graphElement];
    }
}
           

我們添加進去的OC對象,在内部都被轉化成了

FBObjectiveCGraphElement

。裡面大部分操作都使用是

FBObjectiveCGraphElement

對象。在生成該對象的

FBWrapObjectGraphElement()

方法内部,先調用了

_ShouldBreakGraphEdge

,進而直接調用開始配置的過濾

Block

。如果被過濾,直接傳回。對于需要檢測對象,根據類型分别轉化為

FBObjectiveCBlock

FBObjectiveCNSCFTimer

FBObjectiveCObject

,對應OC裡面的

block

NSTimer

Object

。他們都是

FBObjectiveCGraphElement

的子類,這幾個類型主要差別在于

-(NSSet *)allRetainedObjects

方法的實作不同,而這個方法也是比較核心的方法,其功能是擷取對象強引用的所有對象。具體是 怎麼擷取的,我們先跳過,後面詳細介紹。下面先繼續我們檢測邏輯。

  • 引用循環檢測

檢測過程就是周遊第二步建立的

FBObjectiveCGraphElement

數組,進行DFS深度搜尋。然後調用每個節點的

FBObjectiveCGraphElement

對象的

-(NSSet *)allRetainedObjects

方法,拿到所有強引用的對象進行判斷是否循環引用。

我們來通過注釋,詳細的解讀下面的代碼。

- (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

  // 儲存目前DFS搜尋樹中的搜尋路徑
  // 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];
      
      /* 避免重複周遊
		 1.如果不包含(未檢測|已檢測通過),判斷對象位址是否被記錄(是否被檢測過)。已檢測通過的會被移除objectsOnPath數組
		 1.1不包含且已經記錄說明已檢測通過,沒有引用循環,繼續下一個
		 1.2不包含且未被記錄,則記錄位址開始進一步的檢測
	  */
      // 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 */
      /*  
		 這個方法非常的巧妙,會擷取節點(1)本身引用的所有子節點(1.1,1.2,1.3),然後添加到枚舉器裡周遊,并傳回棧頂的一條。
		 如果該子節點不存在引用循環,他将被加到stack變成一個父節點(1.1)。
		 下次周遊就會擷取該子節點引用的所有深一層的子節點(1.1.1),如此循環,這裡預設的最大深度是10。
		 整個子節點周遊完之後,stack又回到了(1)。
		 再下一次nextObject會拿到最開始那個節點(1)的第二個子節點(1.2),重複上面的操作。
		 - (FBNodeEnumerator *)nextObject
		{
		  	if (!_object) {
    			return nil;
  			} else if (!_retainedObjectsSnapshot) {
  				// 擷取所有引用的對象,并過濾封裝為FBObjectiveCGraphElement集合
    			_retainedObjectsSnapshot = [_object allRetainedObjects]; // NSSet <FBObjectiveCGraphElement *>
    			_enumerator = [_retainedObjectsSnapshot objectEnumerator]; // NSEnumerator
  			}
  			FBObjectiveCGraphElement *next = [_enumerator nextObject];
  			if (next) {
    			return [[FBNodeEnumerator alloc] initWithObject:next];
  			}
  			return nil;
		}
	  */
	  // 尋找下一個未通路的節點
      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
        // 如果該節點已經存在被通路過的對象中,說明構成了retain cycle
        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];
      }
    }
  }
  return retainCycles;
}
           

光看代碼确實不那麼清晰明了,我們再結合下面的圖加深了解

[外鍊圖檔轉存失敗,源站可能有防盜鍊機制,建議将圖檔儲存下來直接上傳(img-dMaFdNmz-1594262740501)(evernotecid://27FDE1C0-9B09-42C5-8296-1384110986C8/appyinxiangcom/25025916/ENResource/p2)]

  • 擷取強引用對象

前面說到擷取一個對象所持有的所有對象是通過

-(NSSet *)allRetainedObjects

方法實作的,三種子類都會先調用父類的

-(NSSet *)allRetainedObjects

方法,然後再自己處理。

下面是父類的實作

- (NSSet *)allRetainedObjects
{
    NSArray *retainedObjectsNotWrapped = [FBAssociationManager associationsForObject:_object];
    NSMutableSet *retainedObjects = [NSMutableSet new];
    for (id obj in retainedObjectsNotWrapped) {
        FBObjectiveCGraphElement *element = FBWrapObjectGraphElementWithContext(self,
                                                                                obj,
                                                                                _configuration,
                                                                                @[@"__associated_object"]);
    if (element) {
            [retainedObjects addObject:element];
        }
    }
    return retainedObjects;
}
           

内部調用了

[FBAssociationManager associationsForObject:]

方法,進而調用

associations()

擷取關聯對象。也就是說父類隻擷取關聯對象。

擷取關聯對象的具體流程如下:

  1. 先是通過

    [FBAssociationManager hook]

    方法,修改系統

    objc_setAssociatedObject

    objc_removeAssociatedObjects

    的實作,進而監聽關聯對象的調用
+ (void)hook 
{
#if _INTERNAL_RCD_ENABLED
  std::lock_guard<std::mutex> l(*FB::AssociationManager::hookMutex);
  rcd_rebind_symbols((struct rcd_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
}
           
  1. 每次添加關聯對象,

    FBAssociationManager

    都會将其添加到

    _associationMap

    裡面記錄。這裡隻記錄強引用的關聯對象。
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);
    }
  }
           
  1. 在調用

    associations()

    方法的時候,從

    _associationMap

    裡擷取追蹤到的關聯對象傳回。

    如果沒有調用

    hook

    就無法追蹤關聯對象,是以如果想要檢測關聯對象,需要手動調用一次

    [FBAssociationManager hook]

    方法,如果不檢測關聯對象,可以不調用。
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;
  }
           

具體實作内容較多,有興趣的可以參考如何實作 iOS 中的 Associated Object,裡面對

FBRetainCycleDetector

實作關聯對象這塊做了詳細的解讀。

下面我再看看子類的處理過程

  • FBObjectiveCObject

FBObjectiveCObject

内部依次調用了

FBGetObjectStrongReferences()

->

FBGetStrongReferencesForClass()

->

FBGetClassReferences()

核心的擷取方法是最後一步,前面兩個放法是對擷取結果的封裝及處理。

FBGetClassReferences()

函數裡面利用到了

runtime(class_copyIvarList)

機制去擷取屬性清單,并且封裝為

FBIvarReference

類型。如果遇到屬性是

struct

類型還需單獨進行處理。獲得所有引用後,再通過

class_getIvarLayout

來提取強引用并傳回。最後通過

FBIvarReference

objectReferenceFromObject:

方法擷取引用對象并封裝添加到檢測數組裡(這一步會過濾掉值為

nil

的對象)

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

FBObjectiveCObject

- (NSSet *)allRetainedObjects

實作

- (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) {
    // object_getIvar(object, _ivar);
    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];
      }
    }
  }
  ...
  return retainedObjects;
}
           
  • FBObjectiveCBlock

擷取

Block

持有的強引用的方法是通過

FBGetBlockStrongReferences()

來實作的,而

FBGetBlockStrongReferences()

是對

_GetBlockStrongLayout

擷取結果的封裝。在介紹

_GetBlockStrongLayout

之前,我們需要先簡單了解一下

Block

,他其實是一個結構體,其結構大概是這樣的。

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;
};
           

BlockLiteral

結構體中有一個

isa

指針,而對

isa

了解的人也都知道,這裡的

isa

指向了

Block

的類。

BlockDescriptor

結構體中有兩個函數指針,

copy_helper

用于

Block

的拷貝,

dispose_helper

用于

Block

dispose

也就是

Block

在析構的時候會調用這個函數指針,銷毀自己持有的對象,而這個原理也是差別強弱引用的關鍵,因為在

dispose_helper

會對強引用發送

release

消息,對弱引用不會做任何的處理。

有了這些了解,我們在來詳細解讀

_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.
   如果 block 有 C++ 的構造器/析構器,說明它持有的對象很有可能沒有按照指針大小對齊,我們很難檢測到所有的對象
   如果 block 沒有 dispose 函數,說明它無法 retain 對象,也就是說我們也沒有辦法測試其強引用了哪些對象
   */
  if ((blockLiteral->flags & BLOCK_HAS_CTOR)
      || !(blockLiteral->flags & BLOCK_HAS_COPY_DISPOSE)) {
    return nil;
  }

  // 從 BlockDescriptor 取出 dispose_helper 及大小
  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.
  // 擷取 block 持有的指針的數量
  const size_t elements = (blockLiteral->descriptor->size + ptrSize - 1) / ptrSize;

  // Create a fake object of the appropriate length.
  // 初始化兩個包含 elements 個 FBBlockStrongRelationDetector 執行個體的數組,其中第一個數組用于傳入 dispose_helper,第二個數組用于檢測 _strong 是否被标記為 YES
  void *obj[elements];
  void *detectors[elements];

  for (size_t i = 0; i < elements; ++i) {
  	// 用于從 dispose_helper 接收消息;它的執行個體在接收 release 消息時,并不會真正的釋放,隻會将标記 _strong 為 YES
  	// 因為這個檔案重寫了 release 方法,是以要在非 ARC 下編譯
    FBBlockStrongRelationDetector *detector = [FBBlockStrongRelationDetector new];
    obj[i] = detectors[i] = detector;
  }

  /*
  僞造Block持有的對象,然後将其釋放。
  在自動釋放池中執行 dispose_helper(obj),釋放 block 持有的對象,
  因為 dispose_helper 會調用 release 方法,
  我們重寫 FBBlockStrongRelationDetector 的 release 方法,
  根據release 是都被調用,來标記 _strong 屬性,判斷是否為強引用
  将強引用對象對應索引加入數組,最後再調用 trueRelease 真正的釋放對象
  */
  @autoreleasepool {
  	/*  
  	猜測析構過程是查找原對象是否是強引用,如果是強引用,調用release,
  	而實際上是僞造對象接收了release消息,不僅自己沒釋放,還記錄為強引用
  	等擷取到所有強引用的位置關系之後,才真正釋放掉僞造對象 
  	(僅僅是個人猜測,大家可以檢視源碼深入了解,參考資料後面也貼出來了)
	*/
    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;
}

           

更詳細的内容,請參考iOS 中的 block 是如何持有對象的

這裡使用了一個黑盒技術。建立一個對象來假扮想要調查的Block。因為知道Block的接口,知道在哪可以找到Block持有的引用。僞造的對象将會擁有“釋放檢測(release detectors)”來代替這些引用。釋放檢測器是一些很小的對象,這裡是FBBlockStrongRelationDetector,它們會觀察發送給它們的釋放消息。當持有者想要放棄它的持有的時候,這些消息會發送給強引用對象。當釋放僞造的對象的時候,可以檢測哪些檢測器接收到了這些消息。隻要知道哪些索引在僞造的對象的檢測器中,就可以找到原來Block中實際持有的對象。(出自:FBRetainCycleDetector源碼分析)
  • FBObjectiveCNSCFTimer

    NSTimer所持有的對象基本是确定的,主要

    target

    userInfo

    ,通過

    CFRunLoopTimerGetContext

    擷取

    target

    userInfo

    即可。
補充

可以看到檢測引用循環的效率并不高,而且隻能檢測某個确定的對象,比較雞肋。是以我們應該盡可能的避免引用循環,避免記憶體洩漏。此外可以通過

xcode

自帶工具

Memory Graph Debugger

來檢測,這種方法可能會帶來誤判,需要

review

代碼來确認

1.CF對象

ARC不能管理

Core Foundation

對象的生命周期。是以CF對象需要自己管理記憶體。注意以

creat

copy

作為關鍵字的函數都是需要釋放記憶體的,注意配對使用。比如:

CGColorCreate<-->CGColorRelease

  • __bridge 将CF對象橋接為OC對象,不修改對象記憶體管理權,橋接前後對于被橋接的對象沒有計數的改變
// (ABRecordRef)person 需要自己釋放
 NSString *name = (__bridge NSString *)ABRecordCopyCompositeName((ABRecordRef)person); //擷取聯系人姓名
           
  • __bridge_retained 将OC對象橋接為CF對象,同時将對象(記憶體)的管理權交給我們,後續需要使用

    CFRelease

    或者相關方法來釋放對象。需要注意的是,在調用該函數前要對對象進行

    NULL

    檢查
// dictionaryRef 需要自己釋放
CFDictionaryRef dictionaryRef = (__bridge_retained CFDictionaryRef)self.dict
           
  • __bridge_transfer 将CF對象橋接為OC對象,同時将對象(記憶體)的管理權交給ARC。原有CF對象不需要自己釋放了。

2.NSTimer

_timer

會持有

target

,如果

target

也持有

_timer

,會造成引用循環

[_timer invalidate];
 _timer = nil;
           

3.通知

The return value is retained by the system, and should be held onto by the caller in order to remove the observer with removeObserver: later, to stop observation.
- (void)addObserver {
    // system ->returnValue ->defaultCenter ->Block ->self
    //                      ->Block--------------------↑
    
    // 上面的引用記憶體是該方法做的注釋。要求我們手動移除觀察者 
    self.observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"NotificationCycle" object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification * _Nonnull note) {
        NSLog(@"%s, %@", __func__, self);
    }];

    // 推薦使用此方法
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(doSomething:) name:@"NotificationUserChange" object:nil];
}
           

移除通知

- (void)removeObserver {
    [[NSNotificationCenter defaultCenter] removeObserver:self.observer name:@"NotificationCycle" object:nil];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    self.observer = nil;
}
           

4.

Block

異步執行 需要在

Block

内部,将

weakSelf

轉成

strongSelf

__weak typeof(self) weakSelf = self;
self.changeBlcok = ^{
    [weakSelf doSomething];  // 非異步操作 weakSelf調用可以執行

    /** Block内異步執行
        self在Block完全執行完之前已經釋放,weakSelf也會釋放
        __strong修飾的strongSelf是Block内部的一個局部變量,也就是說作用域僅在Block内部,一旦跳出作用域,那麼我們強制産生的臨時“循環引用”就會被打破
     */
     __strong typeof(weakSelf) strongSelf = weakSelf;
	dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
         [NSThread sleepForTimeInterval:5];
         [strongSelf doSomething];  // weakSelf調用不會被執行
     });
};
           

5.NSURLSession delegate

The session object keeps a strong reference to the delegate until your app exits or explicitly invalidates the session. If you do not invalidate the session by calling the invalidateAndCancel or finishTasksAndInvalidate method, your app leaks memory until it exits.

NSURLSession

delegate

被強引用,使用完需要調用

invalidateAndCancel

finishTasksAndInvalidate

dispatch_queue_t  queue = dispatch_queue_create("SessionQueue", DISPATCH_QUEUE_SERIAL);
 [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:queue];
           

6.地圖類處理

- (void)clearMapView{
     self.mapView = nil;
     self.mapView.delegate =nil;
     self.mapView.showsUserLocation = NO;
     [self.mapView removeAnnotations:self.annotations];
     [self.mapView removeOverlays:self.overlays];
     [self.mapView setCompassImage:nil];
 }
           

7.大次數循環導緻記憶體暴漲,該循環内産生大量的臨時對象,需在循環中建立自己的autoReleasePool,及時釋放占用記憶體大的臨時變量

for (int i = 0; i < 100000; i++) {
        
    // 字元串所占記憶體極小,不明顯
    @autoreleasepool {
        NSString *string = @"Abc";
        string = [string lowercaseString];
        string = [string stringByAppendingString:@"xyz"];
        NSLog(@"%@", string);
    }
 }
           

8.malloc的使用

使用較少,一般使用

CommonCrypto

庫加密用的多一些。正常情況下我們在函數中使用

malloc

一般都會對應

free

,但在使用将

malloc

申請的記憶體作為傳回值的函數時,很有可能遺忘對記憶體的釋放。建議在使用傳回指針的函數時要特别注重這類問題,同時函數的文檔中也需要特别指出傳回值需要調用者手動釋放,避免調用者遺忘。

同時調用者在進行

free

之前,需要對指針進行

NULL

檢測

9.NSURLSessionTask及其子類

在生成

NSURLSessionTask

及其子類對象時,該對象會處于挂起狀态,此時該對象會一直常駐記憶體,若代碼失去對該對象的引用,那麼就會造成記憶體洩漏。

在代碼對

NSURLSessionTask

及其子類對象失去引用前,需要為該對象調用

cancel

resume

方法,使之脫離挂起狀态。