【翻譯】找到iPhone記憶體泄露:Leaks工具指引原文地址:http://www.mobileorchard.com/find-iphone-memory-leaks-a-leaks-tool-tutorial/
see also:http://www.iposei.com/?p=127
我的遊戲開發接近了尾聲,最近常使用Instruments這個工具。我發現它對追蹤遊戲中的記憶體泄露非常有協助。自從發現Instruments如此有用後,我就覺得寫一篇文章介紹如何使用它來追蹤記憶體泄露對其他人也會有協助。
什麼是記憶體泄露?我為什麼要關心記憶體泄露?
…此段省略…
訪問維基百科可以獲得更多關於記憶體泄露的資訊。
我如何知道記憶體泄露了?
一些記憶體泄露可以很容易地通過閱讀代碼來發現,另一些就要困痛點了,這就是為什麼需要Instruments的原因。Instruments有一個“Leaks”工具,它會準確地告訴你什麼地方發生了記憶體泄露,以便你能定位和修複泄露問題。
例子程式
我寫了一個例子程式,它有兩個地方會發生記憶體泄露,一個在Objective-C 視圖控制器中,另一個在C++類中。常式可以從這裡獲得。下邊的代碼是從常式裡摘錄的,包含了我們需要追蹤記憶體泄露的代碼。
// Leaky excerpts – see GitHub for complete source
- (void)viewDidLoad {
[super viewDidLoad];
LeakyClass* myLeakyInstance = new LeakyClass();
delete myLeakyInstance;
mMyLeakyString = [[NSString alloc] initWithUTF8String:”I’m a leaky string.”];
[self doSomethingNow];
}
- (void) doSomethingNow
{
mMyLeakyString = [[NSString alloc] initWithUTF8String:
“Look, another alloc, but no release for first one!”];
}
// Leaky excerpts – see GitHub for complete source
LeakyClass::LeakyClass()
{
mLeakedObject = new LeakedObject();
}
LeakyClass::~LeakyClass()
{
}
我會先在Debug模式編譯InstrumentsTest,並在iPhone上運行。完成這步,我會啟動Instruments。
當你啟動Instruments,你可以從一堆Instruments工具裡選擇你需要的。在左手邊選擇iPhone,在右手邊的表徵圖裡雙擊“Leaks”工具:
之後你會看到下邊的視窗:
請確保iPhone已經串連到了你的電腦,在這個視窗的左上方,你會看到一個下拉式功能表,寫著“Launch Executable”。單擊它,並確保選中的是你iPhone(而不是你的電腦)作為活動裝置。然後移動到“Launch Executable”,你可以看到一個包含了所有已安裝iPhone程式的列表。找到你希望運用“Leaks”工具的程式(本例中是InstrumentsTest)並單擊它。
你已經準備好了。單擊紅色的“Record”按鈕,它會啟動程式並開始記錄程式裡的每個記憶體配置操作。它會每10秒自動地檢測記憶體泄露。
你可以改變多少時間自動檢測一次,你也可以手動進行檢測(檢測記憶體泄露的時候程式會停頓大約3-5秒鐘,如果你想邊進行測試邊進行記憶體檢測的話,這種停頓將會干擾到你)。我一般是設定成手動控制,在我需要的時候才單擊“Check for leaks”按鈕(例如:在loading新的遊戲模式之後檢測一下,在離開遊戲返回MM的時候檢測一下)。單擊“Leaks”,並使用右上方的View->Detail按鈕來設定和查看選項值,在這個例子裡,我將其設定成auto。
程式在運行一段時間之後,自動記憶體檢測將會發現兩處記憶體泄露。太棒了!現在該幹什麼呢?
Extended Detail視圖
Instruments非常懶,它不會明顯地指出下一步該幹什麼。你需要注意的是視窗底部的那一排按鈕。看見兩個矩形組成的那個按鈕了嗎?講你的滑鼠停留在上邊,它會提示“Extended Detail View”。
單擊這個按鈕,右邊將會彈出一個視窗,裡邊提供了各種關於記憶體泄露的詳細資料。單擊一個記憶體泄露,Extended Detail視圖將會顯示泄露的記憶體代碼的完整呼叫堆疊。在我們上邊的例子中,單擊第一個記憶體泄露提示,它發生在[NSString initWithUTF8String]。如果你選中呼叫堆疊裡的高亮步驟,你會看到程式最後一次調用是[InstrumentsTestViewController viewDidLoad]。
雙擊Extend Detail視圖中的某行,它會開啟XCode視窗並顯示出問題的代碼,這是非常棒的功能。
在本例中,第一次NSString分配的時候出現了泄露,你需要做一些處理。這是個非常簡單的例子,但找到為什麼會發生泄露則要麻煩些。讓我們仔細看一下例子。在viewDidLoad當中,我們為字串分配到了記憶體,如下所示:
mMyLeakyString = [[NSString alloc] initWithUTF8String:”I’m a leaky string.”];
在dealloc當中我們用如下方式來釋放
[mMyLeakyString release];
你的直覺可能是這樣不會發生泄露,但搜尋代碼中所有用到了mMyLeakyString的地方,在doSomethingNow中,它是這樣用的:
mMyLeakyString = [[NSString alloc] initWithUTF8String:
“Look, another alloc, but no release for first one!”];
注意,我們聲明了一個新的字串,並且將mMyLeakyString指向了它。這裡的問題是我們沒有在更改mMyLeakyString的指向前釋放它原來指向的記憶體。所以原始的字串依然在堆中,並且我們沒有辦法釋放這部分記憶體。dealloc裡的release操作實際釋放的是我們在doSomethingNow中聲明的字串所佔記憶體,因為這才是指標所指。
為了修複這個問題,我們可以把doSomethingNow改成下邊的代碼:
- (void) doSomethingNow
{
[mMyLeakyString release];
mMyLeakyString = [[NSString alloc] initWithUTF8String:
“Look, another alloc, but released first one!”];
}
這段代碼做的是在我們指定mMyLeakyString到新的字串前釋放第一個字串所佔記憶體。重新編譯運行程式,你會看到只有一個記憶體泄露。當然,在項目中可能有更好的方式來處理NSString,但如果你這樣處理的話可以修複這個泄露問題。
讓我們看看第二個泄露問題。單擊泄露提示看什麼導致了記憶體泄露。發現這個泄露來自於LeakyClass::LeakyClass()建構函式:
在呼叫堆疊中雙擊它,出問題的代碼將會再次出現在XCode中。
我們看到在建構函式裡聲明了一個新的LeakedObject對象,但是解構函式沒有刪除,這樣不好。對於每一個new操作,都需要有與之對應的delete操作。所以我們把解構函式改變成下邊的樣子:
LeakyClass::~LeakyClass()
{
if (mLeakedObject != NULL)
{
delete mLeakedObject;
mLeakedObject = NULL;
}
}
重新編譯運行,沒有記憶體泄露了!
我選擇這兩個例子,雖然非常簡單,但他們展示了Instruments可以用來追蹤Object-C和C++中的記憶體泄露。
修複你的記憶體泄露問題吧,記住,沒有記憶體泄露的程式才是一個好程式。http://www.cnblogs.com/MobileDevelop/tag/記憶體管理/