原文連接地址:http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial程序員
著做權聲明:本文由http://www.cnblogs.com/andyque翻譯,歡迎轉載分享。請尊重做者勞動,轉載時保留該聲明和做者博客連接,謝謝!objective-c
教程截圖:xcode
做爲一名無證程序員,不管你多麼精通Objective-C的內存管理,隨着時間的推移,你也不可避免的犯內存相關的錯誤。但一般由於代碼量太大,以致於你不可能一行一行的去排除(等你解決完,你設計的動車早相撞了!)安全
幸運的是,蘋果已經提供了一些好的方式來幫助你找到應用程序中內存相關的問題。有時,這些工具可能嚇到初學者,但它們實際上至關有用並易於掌握!app
這就是本教程說要介紹的.你會親手使用內存工具在XCode環境下很輕鬆的檢測內存問題。ide
這篇教程是創建在你很是熟悉Objective-C內存管理的基礎上。若是你還在這個問題上找不着北,你可能須要學習內存管理其餘教程。函數
第一步工具
在這一節中,咱們的目的是在一個例子應用程序中檢查、解決任何內存泄漏問題,以演示常見的內存相關錯誤處理。開始,下載一個應用程序示例。我已經將教程和示例工程文件放在一塊兒了。學習
在XCode中打開工程並運行。你會看到tableview中包含了一個壽司列表。試着選擇幾行,而後——轟!你看到可怕的EXC_BAD_ACCESS錯誤,編譯器拿它徹底沒有辦法。ui
由於xcode徹底沒指出出問題的地方,因此這種狀況一般令許多開發者感到鬱悶。當你遇到了一個EXC_BAD_ACCESS錯誤,我一般會給開發者幾個建議:
1.在可執行選項中設置NSZombieEnabled參數,這有時會幫縮小問題的範圍;
2.運行apple的內存檢測工具,如 Leaks ,以便尋找內存問題;
3設定一個斷點,單步運行代碼,直到你找到引發崩潰的位置;
4.註釋代碼,直到不崩潰爲止,而後再從後往前查找錯誤;
如今讓咱們從第一條開始實驗
# 1 - NSZombieEnabled參數
一大波殭屍正在靠近!!!!
不幸的是,NSZombieEnabled選項對於崩潰毫無辦法,因此你徹底能夠放棄抵抗。
當你試圖使用一個已經被銷燬的對象,NSZombieEnabled會標誌一個警告,因此NSZombieEnabled只是一個flag。這是一個良好的開端,由於大多數崩潰的緣由都是使用了已經銷燬的對象。
按照如下設置:在XCode中展開Executables->雙擊PropMemFun->選擇Arguments選項卡->「Variables to be set in the environment」點擊加號按鈕。把變量名值設置成NSZombieEnabled,把值設置成YES,以下圖:(xcode4在左上角,edit schema裏面)
從新運行app,隨便操做下使程序崩潰。查看下console log你就會看到以下信息:
2011-02-0312:07:44.778 PropMemFun[27224:207] ***
-[CFString respondsToSelector:]: message sent to deallocated instance ...
這個程序將在很精確的一行暫停。崩潰後,你能夠經過選定第一個區域,回溯找出致使崩潰的準確行數。好比如今這個示例就崩潰在:tableView:didSelectRowAtIndexPath。
無論你信不信,反正找出了出問題的那行。致使崩潰的問題就是向已經銷燬的string發送了一個消息。這一行用了兩個string:_lastSushiSelected和sushiString.
由於這個string是由stringWithFormat初始化,因此看起來程序是沒有問題了,由於stringWithFormat的返回值是自動釋放的,因此在下次使用前應該是安全的。可是 _lastSushiSelected的安全性如何呢?
雖然_lastSushiSelected是在sushiString執行到最後才賦值的。可是sushiString是自動釋放的,因此有些時候sushiString被釋放了,內存也被銷燬。可是緊接着_lastSushiSelected 仍然有可能指向被銷燬的內存!這就解釋了崩潰緣由:向已經銷燬的內存發送消息致使崩潰。
咱們只需保留_lastSushiSelected就能夠解決這個問題,把最後一行改爲下面的樣子:
_lastSushiSelected = [sushiString retain];
再次運行程序,你會發現程序已經暢通無阻了。
編譯,分析和總結
至少,咱們有一個不崩潰的應用程序——這是一個好的開始。但接下來,咱們須要開始確保沒有任何內存泄漏。
有一種簡單的方法能夠初步確認你的程序在初始化中是否有任何內存泄漏或其餘問題--使用內置編譯和分析功能(built-in Build and Analyze)。
這將使XCode執行你的代碼和自動檢測任何錯誤並警告你任何潛在的問題。它並不會找出全部的問題,但用這個方法找出的錯誤無疑是一個既快速又簡單的方法。
試一試經過選擇Build\Build and Analyze。你應該看到,它檢測到一個內存泄漏,你能夠看到以下:
消息顯示,「alertView」有一個潛在的內存泄漏。若是你看看這一行,你就會發現全部的UIAlertView創造是有着alloc /init (返回一個對象引用數1),卻歷來沒有真正地釋放!有幾種方法能夠解決這個問題,但其中一個方法就是在[alertView show]下面加上一行:
[alertView release];
再次 Build\Build and Analyze,你會發現已經找不出任何內存問題了。
泄漏和管道
不幸的是,你不能依靠Build\Build and Analyze找出一切問題。有一個強大的自動化工具來幫助你檢查程序是否有內存泄漏– the Leaks Instrument。
讓咱們試試看。選擇Run\Run -> Performance -> Tool\Leaks,再選擇table view中的幾行。也能夠上下滾動table view,從table view頂端到底部。基於前面的經驗,你就應該開始看出一些藍色的標籤出如今泄漏的內存上。
點擊中止按鈕,而後去工具欄中點擊「Leaked Blocks」讓他變成「Call Tree」。在面板左下角,點擊「Invert Call Tree」、「Hide System Libraries」。你將會看到這個工具發現兩個不一樣的函數存在內存泄漏,你能夠看到以下:
若是你雙擊一個函數的名字,它會帶你直接到存在內存泄露的這行代碼。這能夠給你一個很好的錯誤位置提示,若是你查看代碼並加以思考,你應該可以找出問題所在並解決它。
因此,爲何不看看代碼,而且看看你是否能找出問題所在並修正嗎?一旦你做出修改,而且可以無錯誤提示的跑Leaks。若是經過,表示你完成了
…
…waiting…
…
…waiitng…
…
…waiting…
…!
你已經搞定了,無論你信不信,反正我是信了。
解釋一下
tableView:didSelectRowAtIndexPath
Leaks 告訴咱們,這個問題的緣由是字符串sushiString創造和存儲過程當中引發的內存泄漏。因此讓咱們一步一步的分析一下緣由:
1.當sushiString被建立時,調用stringWithFormat。返回一個對象數值1而且發送autorelease消息。
2.在方法的最後一行,你在sushiString加入retain(retain數值增長到2)並將其存儲到_lastSushiSelected。
3.後來,autorelease生效,retain數遞減爲1。
4.下一個tableView:didSelectRowAtIndexPath方法被調用,你重寫_lastSushiSelected變量的一個指針指向一個新的字符串,- - - - -若是沒有釋放舊的! 因此那個老字符串並無被釋放仍然存在。
一個解決辦法是增長下面一行在初始化lastSushiSelected sushiString以前:
[_lastSushiSelected release];
tableView:cellForRowAtIndexPath
就像在前面的方法,建立和存入名爲sushiString的變量引發內存泄漏。如下是引發問題的分析:
1.一個新的字符串被alloc/init方法建立。
2.返回一個對象引用數 1.
3.然而,這個計數歷來不減小,因此有一個內存泄漏!
這能夠經過三種方式中的一種解決:
1.設置textLable爲一個字符串後在sushiString中調用release方法。
2.alloc/init方法初始化完畢後在sushiString中調用autorelease。
3.用stringWithFormat代替alloc/init方法,返回一個已經標誌爲自動釋放的字符串。
驗證 leaks!
修正前面提到的問題,再次運行leaks,你會獲得一個沒有任何內存泄漏的app。
接下來該幹什麼?
這個連接能夠下載到一個已經解決上述問題的工程文件。
最重要的是,你必須親自實踐使用NSZombieEnabled,Build and Analyze,和Leaks Instrument工具來找到內存泄漏。你應該可以很快把這項技術運用到你的工程中。
若是你有更好的方法,能夠在下面評論,我也積極採納你們的建議。