iOS 究極體測試工具 內存泄漏

一、檢測工具介紹

1.1 Instrument — Leaks,Allocations,Analyze

我用到的檢測內存泄露的工具主要是Xcode中集成的Leaks組件,這個組件的檢測準確率還是比較高的(畢竟水果家親兒子),可以查看到很多比如說是泄露大小,泄露產生的地方及其堆棧信息等。但是這里的“泄露產生的地方”并不一定可以定位到具體發(fā)生泄漏的某一句代碼,而是會標出發(fā)生泄漏的對象初始化分配內存的地方,然后需要具體去分析該對象來查處泄漏的原因。

參考資料:

1. 【使用Instruments定位iOS應用的Memory Leaks

2. 【Leaks Instrument

Allocations工具是一個跟蹤由應用程序分配的對象內存的工具。一般就是用來在疑似內存泄露的地方,通過反復操作,查看某些對象內存是否有被正常的釋放,從而得知是否發(fā)生內存泄露。(= =。這里我并沒使用到這個,這算是以前比較古老的檢測內存泄漏的方式了,不過某些情況下也還是挺有用。)

參考資料:

1. 【iOS性能優(yōu)化:Instruments 工具的救命三招

2. 【IOS性能調優(yōu)系列:使用Allocation動態(tài)分析內存使用情況

Analyze是一款靜態(tài)分析代碼的工具。它可以發(fā)現(xiàn)一些邏輯錯誤,內存泄漏和聲明錯誤(未使用變量)等。這里可以發(fā)現(xiàn)的一些內存泄漏問題主要是一些常見的循環(huán)引用,CF庫對象未release等相對簡單的問題,通常是在進行其他方式檢測之前就使用的方式,把一些簡單的問題先發(fā)現(xiàn)并處理了。

參考資料:

1. 【IOS性能調優(yōu)系列:Analyze靜態(tài)分析

2. 【IPhone開發(fā)工具篇-利用xcode profile和analyze進行性能優(yōu)化

1.2 內存檢測組件

此外還有一些“植入”項目中的內存檢測組件,比如說Facebook iOS 內存檢測三劍客(FBAllocationTracker/FBMemoryProfiler/FBRetainCycleDetector),MSLeakHunter,MLeaksFinder,PLeakSniffer等等。

這些組件的實現(xiàn)原理都是大同小異的。主要就是靈活運用了OC中的Rumtime機制,以及各種OC對象生命周期管理相關的特性。這些組件為了實現(xiàn)對OC對象的內存監(jiān)控,其本質就是在這些對象被分配和釋放的時機進行監(jiān)測,結合系統(tǒng)對這些對象生命周期管理方法實現(xiàn)是否發(fā)生內存泄露檢測的目的。

比如說需要監(jiān)測一個UIViewController類型的對象,就可以聯(lián)想到iOS中VC的生命周期管理和UINavigationController有很大關系,因為后者在iOS應用者常常被用來管理大量VC的跳轉控制。所以就可以考慮通過監(jiān)控UINavigationController的navigation stack來達到檢測VC是否發(fā)生內存泄露的目的。(一般以下這些方法都會被hook)

再比如說常見的NSObject對象,其alloc和dealloc方法就是對象生命周期中很重要的兩個方法,分別是分配內存資源和釋放內存資源時會被調用的方法。然后就可以考慮通過method swizzing方法替換alloc和dealloc這兩個方法的實現(xiàn),這樣就可以獲得對象內存分配的一些信息。

以下再給出個檢測VC內存泄漏的原理:

如何判斷VC是否還在內存駐留?

Tips:利用ARC中weak指針指向的對象在對象釋放時會自動置為nil的特性來檢測VC是否在內存駐留。

在什么時機檢測VC是否發(fā)生內存泄露?

Tips:通過監(jiān)控UINavigationController的navigation stack,可以判斷一個VC的生命周期的開始和結束。就是當VC從navigation stack移除且VC的viewDidDisappear方法執(zhí)行時,可以認為一個VC的生命周期即將結束。這時候就可以創(chuàng)建一個指向該VC的weak指針,并初始化一個定時器對VC進行延時掃描,最后通過1中的方法判斷VC是否還駐留在內存從而得出VC是否發(fā)生內存泄露的結論。

二、應用內存

從蘋果的開發(fā)者文檔里可以看到,一個 app 的內存分三類:

Leaked memory: Memory unreferenced by your application that cannot be used again or freed (also detectable by using the Leaks instrument).

Abandoned memory: Memory still referenced by your application that has no useful purpose.

Cached memory: Memory still referenced by your application that might be used again for better performance.

其中 Leaked memory 和 Abandoned memory 都屬于應該釋放而沒釋放的內存,都是內存泄露,而 Leaks 工具只負責檢測 Leaked memory,而不管 Abandoned memory。在 MRC 時代 Leaked memory 很常見,因為很容易忘了調用 release,但在 ARC 時代更常見的內存泄露是循環(huán)引用導致的 Abandoned memory,Leaks 工具查不出這類內存泄露,應用有限。(引用出處【傳送門】)

三、實踐

3.1 對象內存管理

1. 在MRC模式下,通過new, copy, alloc方式創(chuàng)建的對象,記得release。一般在delloc中進行釋放操作。當然局部內產生的也要在局部內進行釋放。

點評:呵呵,在實踐中發(fā)現(xiàn)最多的問題就是這個。尤其是在ARC和MRC都有的項目中。= =。我猜測原因之一可能是后面的代碼修改者沒意識到當前修改的文件是MRC模式的,所以在新增一些屬性或成員變量后,沒有在dealloc方法或對象使用完畢后及時的釋放資源。

2. 在MRC模式下,發(fā)送了retain消息,記得也要發(fā)送release消息。并且在一個對象發(fā)送retain消息之前,也要考慮是否要release原來的對象。

碰到的一個栗子:

@interface classA{ NSString *_str; } - (void)functionA{ //正確的方式是這里要有: [_str release]; _str = [[NSString stringWithFormat:@"%d", @(213)] retain]; //后續(xù)代碼... } - (void)functionB{ //正確的方式是這里要有: [_str release]; _str = [[NSString stringWithFormat:@"%d", @(213)] retain]; //后續(xù)代碼... } @end

  • Tips:這里存在一個問題就是functionA中對一個對象發(fā)送了retain消息,如果這時候又調用了functionB方法,str變量被重新賦值。此時如果沒有先對str發(fā)送release消息的話,則會導致functionA中引用的對象發(fā)生內存泄露。

對于一般情況下使用的局部變量都會記得發(fā)送retain后發(fā)送release,然而在栗子中那種情況下,成員變量可能在不同方法中被重新賦值的時候,就要注意了!

3. 不論是MRC還是ARC情況下,使用Core Foundation框架(C語言實現(xiàn)的框架,其可以和Cocoa Foundation庫中的對象進行類型轉換)創(chuàng)建的對象需要手動進行內存管理。即需要手動調用CFRetain和CFRelease來管理對象內存。

Tips:這種情況沒啥好說的了,就是記得CFRetain、CFRelease和retain、release一樣要成對出現(xiàn)~

再多說一點就是Core Foundation框架和Cocoa Foundation對象指針轉換的內容。Cocoa Foundation指針與Core Foundation指針轉換,需要考慮的是所指向對象所有權的歸屬。ARC提供了3個修飾符來管理?!緟⒖假Y料:IOS之Core Foundation框架和Cocoa Foundation框架區(qū)別、Core Foundation Framework Reference

__bridge,什么也不做,僅僅是轉換。此種情況下:

(1). 從Cocoa轉換到Core,需要人工CFRetain,否則,Cocoa指針釋放后, 傳出去的指針則無效。

(2). 從Core轉換到Cocoa,需要人工CFRelease,否則,Cocoa指針釋放后,對象引用計數(shù)仍為1,不會被銷毀。

__bridge_retained,轉換后自動調用CFRetain,即幫助自動解決上述(1)的情形。

__bridge_transfer,轉換后自動調用CFRelease,即幫助自動解決上述(2)的情形。

4. 使用NSAutoreleasePool創(chuàng)建的自動釋放池,一定要確保其發(fā)送drain或release消息。這樣創(chuàng)建的自動釋放池對象才會被釋放,同時被加入自動釋放池的對象才能收到release消息,避免內存泄露。

碰到的栗子:

- (void)functionA{ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *str = [[NSString alloc] initWithFormat:@"%d", @(213)]; [str release]; //執(zhí)行各種代碼... if (...){ //執(zhí)行各種代碼... //問題就在這里:return 之前沒有釋放自動釋放池!??! //正確的做法,加上: [pool release]; return; } [pool release]; }

  • Tips:栗子中的案例雖然看起來是個很逗比的錯誤,不過在實戰(zhàn)中已經發(fā)現(xiàn)兩處了…所以如果是MRC方式下這樣使用自動釋放池時,記得也要對自動釋放池發(fā)送drain或release操作。如果是使用ARC的話,則不推薦栗子中使用自動釋放池的方式,而是下面這種方式了。

@autoreleasepool { // Code benefitting from a local autorelease pool. }


既然說到自動釋放池,那就順便簡單了解一下其實現(xiàn)原理,使用場景和一些注意事項吧。上面也有提到NSAutoreleasePool有兩個方法drain和release,關于這兩者的區(qū)別可以參考這些資料:【NSAutoReleasePool使用中drain和release的區(qū)別】【NSAutoreleasePool】。此外,還發(fā)現(xiàn)了一篇講解AutoReleasePool的比較好的文章,里面也有解釋了AutoReleasePool釋放時間,原理等等:【黑幕背后的Autorelease】。

5. 函數(shù)返回的對象,是否加入自動釋放池(延遲釋放)。從內存管理的規(guī)范上來講,如果一個函數(shù)需要返回一個對象,這個對象應該加入自動釋放池中(”誰創(chuàng)建,誰釋放”)?雖然說從某種角度來說,不加進自動釋放池,而是由函數(shù)調用者負責該對象的釋放也是可行的。如果函數(shù)返回的對象沒有加入自動釋放池,而函數(shù)調用者在外部又沒有釋放該對象,則就有可能造成內存泄露的現(xiàn)象。

(1)OC中有一些對象有多種創(chuàng)建的方法,比如說NSString, NSArray, NSDictionary之類的(還有它們的可變類型)。這些類都提供了兩種類型的創(chuàng)建方式,一種是成員函數(shù)initWithXXX,另一種則是類函數(shù)stringWithXXX, arrayWithXXX(或array), dictionaryWithXXX(或dictionary)這些。

這些方法都是有區(qū)別的,第一種方式產生的對象需要手動release來釋放內存,第二種方式產生的對象已經被加到autoreleasepool中,不需要手動release來釋放內存。所以在項目中也要注意這些對象使用不同創(chuàng)建方式時所采用的不同的對象管理方法,針對這兩種對象生成方式,也有很多討論,大家自己看看吧哈哈哈哈哈。

參考資料:

1.stringWithFormat vs. initWithFormat on NSString

2.objective-C: NSString應該用initWithFormat? 還是 stringWithFormat?

3.Difference between [NSMutableArray array] vs [[NSMutableArray alloc] init]

(2)其中就碰到過Runtime方法中的class_copyIvarList,class_copyMethodList這些方法返回的對象沒有被手動釋放導致的內存泄漏。因為這些是C實現(xiàn)的函數(shù),是需要手動對函數(shù)返回值進行free的,不然則會導致內存泄露。= =。這里也順便提醒平時需要注意對于C/C++的實現(xiàn),當見到malloc/new分配的對象,就應該檢查該對象有沒有對應的free/delete操作,這些地方往往也是內存泄漏產生的地方。

3.2 引用循環(huán)

這是無論在MRC還是ARC下都存在的一種導致內存泄露的情況,尤其是在ARC中,如果發(fā)生內存泄漏,其一般都會是罪魁禍首。= =。而且個人覺得引用循環(huán)這種問題是最難發(fā)現(xiàn)和分析的!項目很大的時候,模塊間會很復雜,相互間依賴就很多,一不小心就很容易產生強引用循環(huán)這種現(xiàn)象。尤其是在使用到block的時候,更要注意適當處理以避免強引用循環(huán)的發(fā)生。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容