1. 程式人生 > >如何在 iOS 中解決迴圈引用的問題

如何在 iOS 中解決迴圈引用的問題

稍有常識的人都知道在 iOS 開發時,我們經常會遇到迴圈引用的問題,比如兩個強指標相互引用,但是這種簡單的情況作為稍有經驗的開發者都會輕鬆地查找出來。

但是遇到下面這樣的情況,如果只看其實現程式碼,也很難僅僅憑藉肉眼上的觀察以及簡單的推理就能分析出其中存在的迴圈引用問題,更何況真實情況往往比這複雜的多:

Objective-C
123456789 testObject1.object=testObject2;testObject1.secondObject=testObject3;testObject2.object=testObject4;testObject2.secondObject=testObject5;testObject3.object=testObject1;testObject5.object=testObject6;testObject4.object=testObject1;testObject5.secondObject
=testObject7;testObject7.object=testObject2;

上述程式碼確實是存在迴圈引用的問題:

111975281-73d38f8f3a55de33 detector-retain-objects

這一次分享的內容就是用於檢測迴圈引用的框架 FBRetainCycleDetector 我們會分幾個部分來分析 FBRetainCycleDetector 是如何工作的:

  1. 檢測迴圈引用的基本原理以及過程
  2. 檢測設計 NSObject 物件的迴圈引用問題
  3. 檢測涉及 Associated Object 關聯物件的迴圈引用問題
  4. 檢測涉及 Block 的迴圈引用問題

這是四篇文章中的第一篇,我們會以類 FBRetainCycleDetector

- findRetainCycles 方法為入口,分析其實現原理以及執行過程。

簡單介紹一下 FBRetainCycleDetector 的使用方法:

Objective-C
12345678 _RCDTestClass *testObject=[_RCDTestClass new];testObject.object=testObject;FBRetainCycleDetector*detector=[FBRetainCycleDetectornew];[detector addCandidate:testObject];NSSet*retainCycles=[detector findRetainCycles];NSLog(@"%@",retainCycles);
  1. 初始化一個 FBRetainCycleDetector 的例項
  2. 呼叫 - addCandidate: 方法新增潛在的洩露物件
  3. 執行 - findRetainCycles 返回 retainCycles

在控制檯中的輸出是這樣的:

Objective-C
12345 2016-07-29 15:26:42.043xctest[30610:1003493]{(("-> _object -> _RCDTestClass "))}

說明 FBRetainCycleDetector 在程式碼中發現了迴圈引用。

findRetainCycles 的實現

在具體開始分析 FBRetainCycleDetector 程式碼之前,我們可以先觀察一下方法 findRetainCycles 的呼叫棧:

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

呼叫棧中最上面的兩個簡單方法的實現都是比較容易理解的:

Objective-C
123456789101112131415 -(NSSet*>*)findRetainCycles{return[self findRetainCyclesWithMaxCycleLength:kFBRetainCycleDetectorDefaultStackDepth];}-(NSSet*>*)findRetainCyclesWithMaxCycleLength:(NSUInteger)length{NSMutableSet*>*allRetainCycles=[NSMutableSetnew];for(FBObjectiveCGraphElement*graphElementin_candidates){NSSet*>*retainCycles=[self _findRetainCyclesInObject:graphElement                                                                                          stackDepth:length];[allRetainCycles unionSet:retainCycles];}[_candidates removeAllObjects];returnallRetainCycles;}

- findRetainCycles 呼叫了 - findRetainCyclesWithMaxCycleLength: 傳入了 kFBRetainCycleDetectorDefaultStackDepth 引數來限制查詢的深度,如果超過該深度(預設為 10)就不會繼續處理下去了(查詢的深度的增加會對效能有非常嚴重的影響)。

- findRetainCyclesWithMaxCycleLength: 中,我們會遍歷所有潛在的記憶體洩露物件 candidate,執行整個框架中最核心的方法 - _findRetainCyclesInObject:stackDepth:,由於這個方法的實現太長,這裡會分幾塊對其進行介紹,並會省略其中的註釋:

Objective-C
1234567891011 -(NSSet*>*)_findRetainCyclesInObject:(FBObjectiveCGraphElement*)graphElement                                                                 stackDepth:(NSUInteger)stackDepth{NSMutableSet*>*retainCycles=[NSMutableSetnew];FBNodeEnumerator*wrappedObject=[[FBNodeEnumeratoralloc] initWithObject:graphElement];NSMutableArray*stack=[NSMutableArraynew];NSMutableSet*objectsOnPath=[NSMutableSetnew];...}

其實整個物件的相互引用情況可以看做一個有向圖,物件之間的引用就是圖的 Edge,每一個物件就是 Vertex查詢迴圈引用的過程就是在整個有向圖中查詢環的過程,所以在這裡我們使用 DFS 來掃面圖中的環,這些環就是物件之間的迴圈引用。

文章中並不會介紹 DFS 的原理,如果對 DFS 不瞭解的讀者可以看一下這個視訊,或者找以下相關資料瞭解一下 DFS 的實現。

接下來就是 DFS 的實現:

Objective-C
12345678910111213141516171819202122232425262728293031323334 -(NSSet*>*)_findRetainCyclesInObject:(FBObjectiveCGraphElement*)graphElement                                                                 stackDepth:(NSUInteger)stackDepth{...[stack addObject:wrappedObject];while([stack count]>0){@autoreleasepool{FBNodeEnumerator*top=[stack lastObject];[objectsOnPath addObject:top];FBNodeEnumerator*firstAdjacent=[top nextObject];if(firstAdjacent){BOOLshouldPushToStack=NO;if([objectsOnPath containsObject:firstAdjacent]){NSUIntegerindex=[stack indexOfObject:firstAdjacent];NSIntegerlength=[stack count]-index;if(index==NSNotFound){shouldPushToStack=YES;}else{NSRangecycleRange=NSMakeRange(index,length);NSMutableArray*cycle=[[stack subarrayWithRange:cycleRange] mutableCopy];[cycle replaceObjectAtIndex:0 withObject:firstAdjacent];[retainCycles addObject:[self _shiftToUnifiedCycle:[self _unwrapCycle:cycle]]];}}else{shouldPushToStack=YES;}if(shouldPushToStack){if([stack count]

這裡其實就是對 DFS 的具體實現,其中比較重要的有兩點,一是使用 nextObject 獲取下一個需要遍歷的物件,二是對查詢到的環進行處理和篩選;在這兩點之中,第一點相對重要,因為 nextObject 的實現是呼叫 allRetainedObjects 方法獲取被當前物件持有的物件,如果沒有這個方法,我們就無法獲取當前物件的鄰接結點,更無從談起遍歷了:

Objective-C
12345678910111213141516 -(FBNodeEnumerator*)nextObject{if(!_object){returnnil;}elseif(!_retainedObjectsSnapshot){_retainedObjectsSnapshot=[_object allRetainedObjects];_enumerator=[_retainedObjectsSnapshot objectEnumerator];}FBObjectiveCGraphElement*next=[_enumerator nextObject];if(next){return[[FBNodeEnumeratoralloc] initWithObject:next];}returnnil;}

基本上所有圖中的物件 FBObjectiveCGraphElement 以及它的子類 FBObjectiveCBlock FBObjectiveCObjectFBObjectiveCNSCFTimer 都實現了這個方法返回其持有的物件陣列。獲取陣列之後,就再把其中的物件包裝成新的 FBNodeEnumerator 例項,也就是下一個 Vertex

因為使用 - subarrayWithRange: 方法獲取的陣列中的物件都是 FBNodeEnumerator 的例項,還需要一定的處理才能返回:

    • (NSArray)_unwrapCycle:(NSArray> *)cycle
    • (NSArray)_shiftToUnifiedCycle:(NSArray> *)array

- _unwrapCycle: 的作用是將陣列中的每一個 FBNodeEnumerator 例項轉換成 FBObjectiveCGraphElement

Objective-C
12345678 -(NSArray*)_unwrapCycle:(NSArray*)cycle{NSMutableArray*unwrappedArray=[NSMutableArraynew];for(FBNodeEnumerator*wrappedincycle){[unwrappedArray addObject:wrapped.object];}returnunwrappedArray;}

- _shiftToUnifiedCycle: 方法將每一個環中的元素按照地址遞增以及字母順序來排序,方法簽名很好的說明了它們的功能,兩個方法的程式碼就不展示了,它們的實現沒有什麼值得注意的地方:

Objective-C
123 -(NSArray*)_shiftToUnifiedCycle:(NSArray*

相關推薦

如何在 iOS 解決迴圈引用的問題

稍有常識的人都知道在 iOS 開發時,我們經常會遇到迴圈引用的問題,比如兩個強指標相互引用,但是這種簡單的情況作為稍有經驗的開發者都會輕鬆地查找出來。 但是遇到下面這樣的情況,如果只看其實現程式碼,也很難僅僅憑藉肉眼上的觀察以及簡單的推理就能分析出其中存在的迴圈引用問題,更

swift解決迴圈引用的方法

// 方法1: OC 的方法 // weakSelf -> ViewController?  // self - ViewController // 'weak' must be a mutable variable, because it may change a

淺談iOS迴圈引用問題

關於iOS中迴圈引用的場景: 1、代理 delegate      這個一般是不會出錯的,ARC建立代理的時候我們用的是weak,MRC下我們用的assign。基本就避免了這個問題。 2、block

IOS解決ARC類例項間迴圈引用(Swfit)

原創Blog,轉載請註明出處http://blog.csdn.net/column/details/swfitexperience.html 備註:本文程式碼和圖片主要來自於官方文件 不熟悉ARC的同學可以看看前一篇關於ARC的簡述,這個是我的Swfit教程專欄http:/

ios-Swift解除迴圈引用的三種方式

1、加一個標記,表示裡面用到的self都是弱引用 test4 {[weak self] (name)->() in //self?表示如果物件一旦被

淺談OC迴圈引用問題

將近一個月沒有寫文章了,藉口就不一一羅列了。。。。 廢話不多說,進入正題。 談到迴圈引用這個問題,相信很多iOS的童鞋至少都在運用block技術的時候遇到過,同樣的,很多童鞋肯定也是通過weak這個關鍵字來處理的,但是我相信,這其中肯定有不少童鞋並沒有搞明白為什麼會發生迴圈引用。本篇短文章,就以自己的理解

vue 解決迴圈引用元件報錯的問題

問題由來 最近在做專案的時候遇到使用迴圈元件,因為模式一樣,只有資料不一樣。按照普通元件呼叫格式來做的時候總是報錯,錯誤資訊為[Vue warn]: Unknown custom element: <selfile> - did you register

【原】iOS容易造成迴圈引用的三種場景,就在你我身邊!

ARC已經出來很久了,自動釋放記憶體的確很方便,但是並非絕對安全絕對不會產生記憶體洩露。導致iOS物件無法按預期釋放的一個無形殺手是——迴圈引用。迴圈引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前物件是一個ViewControll

iOS Block的迴圈引用問題

在iOS開發中,block在不同介面的回撥傳值有著舉足輕重的分量,但如果用的不恰當的話,可能會引發記憶體問題,在微信公眾號和部落格中都看到這篇文章,感覺說的在理,就在這裡分享給大家了,希望能對大家有幫助。 前言 本篇文章精講iOS開發中使用Block時一定

iOS容易造成迴圈引用的三種場景之Block以及對應的使用方法(二)

         可以看到在Block結構體中含有isa指標,這就證明了Block其實就是物件,並具有一般物件的所有功能。這個isa指標被初始化為_NSConcreteStackBlock或者_NSConcreteGlobalBlock類的地址。在沒有開啟ARC的情況下,如果Block中包含有區域性變數則is

block解決迴圈引用(二)

接著上一遍,這裡首先講一下 block的反向傳值 ViewController和ViewController1 //ViewController1裡面 typedef void(^MyBl

block解決迴圈引用(一)

看了網上的一些資料,感覺暈乎乎的,還不是很明白。自己抽了一個下午仔細的研究了一下block,嗯,明白了好多。分兩次寫出來,歡迎大家交流。 這裡不討論底層,不討論原理,注重實用! 什麼是block? 閉包(block):閉包就是獲取其他函

javascript迴圈引用物件處理

先說明一下什麼是迴圈引用物件: var a={"name":"zzz"}; var b={"name":"vvv"}; a.child=b; b.parent=a; 複製程式碼 這裡的a和b都是一個迴圈引用物件。迴圈引用物件本來沒有什麼問題,序列化的時候才會發生問題,比如呼叫JSON.stringif

GC可達性分析回收演算法 解決迴圈引用問題 強引用引用

JVM有一個回收演算法是引用計數演算法,每當物件被引用一次,就+1,釋放一個引用就-1,當垃圾回收時,引用計數為0的物件就會被GC掉。但這個方法有個問題,就是無法解決迴圈引用的問題。 迴圈引用就是物件A引用了物件B,物件B引用了物件A,構成了一個引用環。彼此都沒發揮什麼作用

Swift學習記錄 -- 14.閉包的使用和解決迴圈引用方法

Swift中的閉包 , 幾乎和OC中的block一模一樣 , 我個人又比較偏好block , 所以覺得閉包還是蠻不錯的 . 在迴圈引用問題上 , 解決方案也更加簡潔 // HttpTool類

vue-cli解決css引用圖片打包後找不到檔案資源的問題

1.在CSS中引入圖片   #slider1 { background-image: url(./bg02.jpg); background-size: cover; } 注意:此處的圖片與索引檔案在同一個目錄下; 在開發環境下背景圖片是可以好好的顯示的,但是

Flask-分開Models解決迴圈引用

在之前我們測試中,所有語句都在同一個檔案中,但隨著專案越來越大,管理起來有所不便,所以將Models分離. 基本的檔案結構如下 \—–app.py \—–models.py from flask

小胖說swift07-------- swift協議代理的使用以及解決迴圈引用問題

這兩天看了一下Swift的協議代理, 大體思路和OC沒什麼區別, 但是按照官方的書本寫出的協議代理, 發現會有記憶體洩露問題, 找了半天沒有發現問題, 突然想起看系統類的協議代理的寫法, 瞬間發現了

iOS無限迴圈滾動簡單處理實現

說下原理: 1./*初始化/ 1 + (instancetype)loopScrollViewWithFrame:(CGRect)frame; 將背景collectinview檢視初始化設定 代理和資料來

vue 解決迴圈引用元件報錯

做專案時遇到使用迴圈元件,因為模式一樣,只有資料不一樣。但是按照普通的元件呼叫格式來做時報錯,錯誤資訊為Unknown custom element: <pop> - did you register the component correctly? For r