原文鏈接地址:http://www.raywenderlich.com/2696/how-to-debug-memory-leaks-with-xcode-and-instruments-tutorial
免責申明(必讀!):本博客提供的所有教程的翻譯原稿均來自於互聯網,僅供學習交流之用,切勿進行商業傳播。同時,轉載時不要移除本申明。如產生任何糾紛,均與本博客所有人、發表該翻譯稿之人無任何關係。謝謝合作!
本文由yy翻譯。Powered by YY!:)
教程截圖:
a
作爲一名無證程序員,無論你多麼精通Objective-C的內存管理,隨着時間的推移,你也不可避免的犯內存相關的錯誤。但通常因爲代碼量太大,以至於你不可能一行一行的去排除(等你解決完,你設計的動車早相撞了!)
幸運的是,蘋果已經提供了一些好的方式來幫助你找到應用程序中內存相關的問題。有時,這些工具可能嚇到初學者,但它們實際上相當有用並易於掌握!
這就是本教程說要介紹的.你會親手使用內存工具在XCode環境下很輕鬆的檢測內存問題。
這篇教程是建立在你非常熟悉Objective-C內存管理的基礎上。如果你還在這個問題上找不着北,你可能需要學習內存管理其他教程。
第一步
在這一節中,我們的目的是在一個例子應用程序中檢查、解決任何內存泄漏問題,以演示常見的內存相關錯誤處理。開始,下載一個應用程序示例。我已經將教程和示例工程文件放在一起了。
在XCode中打開工程並運行。你會看到tableview中包含了一個壽司列表。試着選擇幾行,然後——轟!你看到可怕的EXC_BAD_ACCESS錯誤,編譯器拿它完全沒有辦法。
因爲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-03 12: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工具來找到內存泄漏。你應該能夠很快把這項技術運用到你的工程中。
如果你有更好的方法,可以在下面評論,我也積極採納大家的建議。