iOS 究極體測試工具 內(nèi)存泄漏

一郑气、檢測工具介紹

1.1 Instrument — Leaks柠并,Allocations弛饭,Analyze

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

參考資料:

1. 【使用Instruments定位iOS應(yīng)用的Memory Leaks

2. 【Leaks Instrument

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

參考資料:

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

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

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

參考資料:

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

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

1.2 內(nèi)存檢測組件

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

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

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

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

以下再給出個檢測VC內(nèi)存泄漏的原理:

如何判斷VC是否還在內(nèi)存駐留炒辉?

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

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

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

二霎苗、應(yīng)用內(nèi)存

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

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 都屬于應(yīng)該釋放而沒釋放的內(nèi)存搀崭,都是內(nèi)存泄露,而 Leaks 工具只負(fù)責(zé)檢測 Leaked memory猾编,而不管 Abandoned memory瘤睹。在 MRC 時代 Leaked memory 很常見,因為很容易忘了調(diào)用 release答倡,但在 ARC 時代更常見的內(nèi)存泄露是循環(huán)引用導(dǎo)致的 Abandoned memory轰传,Leaks 工具查不出這類內(nèi)存泄露,應(yīng)用有限瘪撇。(引用出處【傳送門】)

三获茬、實踐

3.1 對象內(nèi)存管理

1. 在MRC模式下港庄,通過new, copy, alloc方式創(chuàng)建的對象,記得release恕曲。一般在delloc中進(jìn)行釋放操作鹏氧。當(dāng)然局部內(nèi)產(chǎn)生的也要在局部內(nèi)進(jìn)行釋放。

點評:呵呵佩谣,在實踐中發(fā)現(xiàn)最多的問題就是這個把还。尤其是在ARC和MRC都有的項目中。= =茸俭。我猜測原因之一可能是后面的代碼修改者沒意識到當(dāng)前修改的文件是MRC模式的吊履,所以在新增一些屬性或成員變量后,沒有在dealloc方法或?qū)ο笫褂猛戤吅蠹皶r的釋放資源调鬓。

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消息燕锥,如果這時候又調(diào)用了functionB方法辜贵,str變量被重新賦值。此時如果沒有先對str發(fā)送release消息的話归形,則會導(dǎo)致functionA中引用的對象發(fā)生內(nèi)存泄露托慨。

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

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

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

再多說一點就是Core Foundation框架和Cocoa Foundation對象指針轉(zhuǎn)換的內(nèi)容谐区。Cocoa Foundation指針與Core Foundation指針轉(zhuǎn)換,需要考慮的是所指向?qū)ο笏袡?quán)的歸屬逻卖。ARC提供了3個修飾符來管理宋列。【參考資料:IOS之Core Foundation框架和Cocoa Foundation框架區(qū)別评也、Core Foundation Framework Reference

__bridge炼杖,什么也不做灭返,僅僅是轉(zhuǎn)換。此種情況下:

(1). 從Cocoa轉(zhuǎn)換到Core坤邪,需要人工CFRetain熙含,否則,Cocoa指針釋放后罩扇, 傳出去的指針則無效婆芦。

(2). 從Core轉(zhuǎn)換到Cocoa,需要人工CFRelease喂饥,否則消约,Cocoa指針釋放后,對象引用計數(shù)仍為1员帮,不會被銷毀或粮。

__bridge_retained,轉(zhuǎn)換后自動調(diào)用CFRetain捞高,即幫助自動解決上述(1)的情形氯材。

__bridge_transfer,轉(zhuǎn)換后自動調(diào)用CFRelease硝岗,即幫助自動解決上述(2)的情形氢哮。

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

碰到的栗子:

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

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

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


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

5. 函數(shù)返回的對象,是否加入自動釋放池(延遲釋放)祥国。從內(nèi)存管理的規(guī)范上來講昵观,如果一個函數(shù)需要返回一個對象,這個對象應(yīng)該加入自動釋放池中(”誰創(chuàng)建舌稀,誰釋放”)啊犬?雖然說從某種角度來說,不加進(jìn)自動釋放池壁查,而是由函數(shù)調(diào)用者負(fù)責(zé)該對象的釋放也是可行的觉至。如果函數(shù)返回的對象沒有加入自動釋放池,而函數(shù)調(diào)用者在外部又沒有釋放該對象睡腿,則就有可能造成內(nèi)存泄露的現(xiàn)象语御。

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

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

參考資料:

1.stringWithFormat vs. initWithFormat on NSString

2.objective-C: NSString應(yīng)該用initWithFormat? 還是 stringWithFormat?

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

(2)其中就碰到過Runtime方法中的class_copyIvarList抵卫,class_copyMethodList這些方法返回的對象沒有被手動釋放導(dǎo)致的內(nèi)存泄漏。因為這些是C實現(xiàn)的函數(shù)胎撇,是需要手動對函數(shù)返回值進(jìn)行free的介粘,不然則會導(dǎo)致內(nèi)存泄露。= =晚树。這里也順便提醒平時需要注意對于C/C++的實現(xiàn)姻采,當(dāng)見到malloc/new分配的對象,就應(yīng)該檢查該對象有沒有對應(yīng)的free/delete操作爵憎,這些地方往往也是內(nèi)存泄漏產(chǎn)生的地方慨亲。

3.2 引用循環(huán)

這是無論在MRC還是ARC下都存在的一種導(dǎo)致內(nèi)存泄露的情況婚瓜,尤其是在ARC中,如果發(fā)生內(nèi)存泄漏刑棵,其一般都會是罪魁禍?zhǔn)住? =巴刻。而且個人覺得引用循環(huán)這種問題是最難發(fā)現(xiàn)和分析的!項目很大的時候蛉签,模塊間會很復(fù)雜胡陪,相互間依賴就很多,一不小心就很容易產(chǎn)生強(qiáng)引用循環(huán)這種現(xiàn)象碍舍。尤其是在使用到block的時候柠座,更要注意適當(dāng)處理以避免強(qiáng)引用循環(huán)的發(fā)生。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末片橡,一起剝皮案震驚了整個濱河市愚隧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌锻全,老刑警劉巖狂塘,帶你破解...
    沈念sama閱讀 211,376評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鳄厌,居然都是意外死亡荞胡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,126評論 2 385
  • 文/潘曉璐 我一進(jìn)店門了嚎,熙熙樓的掌柜王于貴愁眉苦臉地迎上來泪漂,“玉大人,你說我怎么就攤上這事歪泳÷芮冢” “怎么了?”我有些...
    開封第一講書人閱讀 156,966評論 0 347
  • 文/不壞的土叔 我叫張陵呐伞,是天一觀的道長敌卓。 經(jīng)常有香客問我,道長伶氢,這世上最難降的妖魔是什么趟径? 我笑而不...
    開封第一講書人閱讀 56,432評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮癣防,結(jié)果婚禮上蜗巧,老公的妹妹穿的比我還像新娘。我一直安慰自己蕾盯,他們只是感情好幕屹,可當(dāng)我...
    茶點故事閱讀 65,519評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般望拖。 火紅的嫁衣襯著肌膚如雪迅腔。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,792評論 1 290
  • 那天靠娱,我揣著相機(jī)與錄音,去河邊找鬼掠兄。 笑死像云,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蚂夕。 我是一名探鬼主播迅诬,決...
    沈念sama閱讀 38,933評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼婿牍!你這毒婦竟也來了侈贷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,701評論 0 266
  • 序言:老撾萬榮一對情侶失蹤等脂,失蹤者是張志新(化名)和其女友劉穎俏蛮,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體上遥,經(jīng)...
    沈念sama閱讀 44,143評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡搏屑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,488評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了粉楚。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辣恋。...
    茶點故事閱讀 38,626評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖模软,靈堂內(nèi)的尸體忽然破棺而出伟骨,到底是詐尸還是另有隱情,我是刑警寧澤燃异,帶...
    沈念sama閱讀 34,292評論 4 329
  • 正文 年R本政府宣布携狭,位于F島的核電站,受9級特大地震影響回俐,放射性物質(zhì)發(fā)生泄漏暑中。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,896評論 3 313
  • 文/蒙蒙 一鲫剿、第九天 我趴在偏房一處隱蔽的房頂上張望鳄逾。 院中可真熱鬧,春花似錦灵莲、人聲如沸雕凹。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,742評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枚抵。三九已至线欲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間汽摹,已是汗流浹背李丰。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留逼泣,地道東北人趴泌。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像拉庶,于是被迫代替她去往敵國和親嗜憔。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,494評論 2 348

推薦閱讀更多精彩內(nèi)容