1. 程式人生 > 其它 >iOS學習——記憶體洩漏檢查及原因分析

iOS學習——記憶體洩漏檢查及原因分析

專案的程式碼很多,前兩天老大突然跟我說專案中某一個ViewController的dealloc()方法沒有被呼叫,存在記憶體洩漏問題,需要排查原因,解決記憶體洩漏問題。由於剛加入專案組不久,對出問題的模組的程式碼還不太熟悉,所以剛拿到問題時覺得很棘手,再加上作為一個iOS菜鳥,對記憶體洩漏的排查方法和原因確實基本上不了解。所以,也藉著這樣的機會,我研究了一下關於iOS開發中記憶體洩漏的排查方法和原因分析。

  首先,補充兩個基本概念的解釋:

  • 記憶體溢位 (out of memory):是指程式在申請記憶體時,沒有足夠的記憶體空間供其使用,出現out of memory。通俗理解就是記憶體不夠,通常在執行大型軟體或遊戲時,軟體或遊戲所需要的記憶體遠遠超出了你主機內安裝的記憶體所承受大小,就叫記憶體溢位。
  • 記憶體洩露( memory leak):是指程式在申請記憶體後,無法釋放已申請的記憶體空間,一次記憶體洩露危害可以忽略,但記憶體洩露堆積後果很嚴重,無論多少記憶體,遲早會被佔光。

一、排查方法

我們知道,iOS開發中對記憶體管理的要求非常嚴格,一旦存在記憶體洩漏,後果是非常嚴重的,會導致程式非常容易崩潰。儘管目前iOS開發基本上都是採用的ARC方式進行記憶體管理,但是一不小心就會存在記憶體洩漏的問題。

  首先,我們需要定位記憶體洩漏的問題,目前比較常用的記憶體洩漏的排查方法有兩種,都在xcode中可以直接使用:靜態分析方法(Analyze)和動態分析方法(Instrument的leak)。

1.1 靜態記憶體洩漏分析方法

通過xcode開啟專案,然後點選product-->Analyze,如下圖左側的圖所示,這樣就開始對專案進行靜態記憶體洩漏分析,分析結果如下圖右側的圖所示。根據分析結果進行休整之後在進行分析就好了。

靜態分析方法能發現大部分的問題,但是隻能是靜態分析結果,有一些並不準確,還有一些動態分配記憶體的情形並沒有進行分析。所以僅僅使用靜態記憶體洩漏分析得到的結果並不是非常可靠,如果需要,我們需要將對專案進行更為完善的記憶體洩漏分析和排查。那就需要用到我們下面要介紹的動態記憶體洩漏分析方法Instruments中的Leaks方法進行排查。

1.2 動態記憶體洩漏分析方法

分析記憶體洩露不能把所有的記憶體洩露查出來,有的記憶體洩露是在執行時,使用者操作時才產生的。那就需要用到Instruments了。具體操作是通過xcode開啟專案,然後點選product-->profile,如下圖左側圖所示。

按上面操作,build成功後跳出Instruments工具,如上圖右側圖所示。選擇Leaks選項,點選右下角的【choose】按鈕,這時候專案程式也在模擬器或手機上執行起來了,在手機或模擬器上對程式進行操作,工具顯示效果如下:

點選左上角的紅色圓點,這時專案開始啟動了,由於leaks是動態監測,所以手動進行一系列操作,可檢查專案中是否存在記憶體洩漏問題。如圖所示,橙色矩形框中所示綠色為正常,如果出現如右側紅色矩形框中顯示紅色,則表示出現記憶體洩漏。

選中Leaks Checks,在Details所在欄中選擇CallTree,並且在右下角勾選Invert Call Tree 和Hide System Libraries,會發現顯示若干行程式碼,雙擊即可跳轉到出現記憶體洩漏的地方,修改即可。

二、記憶體洩漏的原因分析

在目前主要以ARC進行記憶體管理的開發模式,導致記憶體洩漏的根本原因是程式碼中存在迴圈引用,從而導致一些記憶體無法釋放,這就會導致dealloc()方法無法被呼叫。主要原因大概有一下幾種型別。

2.1 ViewController中存在NSTimer

如果你的ViewController中有NSTimer,那麼你就要注意了,因為當你呼叫

[NSTimer scheduledTimerWithTimeInterval:1.0 
                                 target:self 
                               selector:@selector(updateTime:) 
                               userInfo:nil 
                                repeats:YES];
時的 target:self 就增加了ViewController的return count,如果你不將這個timer invalidate,將別想呼叫dealloc。

2.2 ViewController中的代理delegate

  一個比較隱祕的因素,你去找找與這個類有關的代理,有沒有強引用屬性?如果你這個VC需要外部傳某個Delegate進來,來通過Delegate+protocol的方式傳引數給其他物件,那麼這個delegate一定不要強引用,儘量assign或者weak,否則你的VC會持續持有這個delegate,直到它自身被釋放。

2.3 ViewController中Block

這個可能就是經常容易犯的一個問題了,Block體內使用例項變數也會造成迴圈引用,使得擁有這個例項的物件不能釋放。因為該block本來就是當前viewcontroller的一部分,現在蓋子部門又強引用self,導致迴圈引用無法釋放。 例如你這個類叫OneViewController,有個屬性是NSString *name; 如果你在block體中使用了self.name,或者_name,那樣子的話這個類就沒法釋放。 要解決這個問題其實很簡單,就是在block之前申明當前的self引用為弱引用即可。

//MRC下程式碼如下
__block Viewcontroller *weakSelf = self;
//ARC下程式碼如下
__weak Viewcontroller *weakSelf = self;

2.4 ViewController的子檢視對self的持有

這個問題也是我的專案中記憶體洩漏的問題所在。我們有時候需要在子檢視或者某個cell中點選跳轉等操作,需要在子檢視或cell中持有當前的ViewController物件,這樣跳轉之後的back鍵才能直接返回該頁面,同時也不銷燬當前ViewController。此時,你就要注意在子檢視或者cell中對當前頁面的持有物件不能是強引用,儘量assign或者weak,否則會造成迴圈引用,記憶體無法釋放。