性能對(duì)于一款app來說至關(guān)重要,而程序的內(nèi)存占用情況就是一項(xiàng)很重要的性能指標(biāo)。雖然iOS 5.0版本之后加入了ARC機(jī)制,但由于相互引用關(guān)系比較復(fù)雜時(shí),內(nèi)存泄露還是可能存在。
文章脈絡(luò):

一、內(nèi)存優(yōu)化
簡(jiǎn)介:Objective_C 有3種內(nèi)存管理方法, 它們分別是
- MRR (Manual Retain Release, 手動(dòng)保持釋放)- ARC(Automatic Reference Counting, 自動(dòng)引用計(jì)數(shù))- GC(Garbage Collection, 垃圾收集)
1>MRR① 也稱為 MRC(Manual Reference Counting, 手動(dòng)引用計(jì)數(shù))② 由程序員自己負(fù)責(zé)管理對(duì)象生命周期,負(fù)責(zé)對(duì)象的創(chuàng)建和銷毀.
2>ARC① 采用和 MRR 一樣的內(nèi)存引用計(jì)數(shù)管理方法。② 在編譯時(shí)會(huì)在適合的位置插入對(duì)象內(nèi)存釋放, (如 release, autorelease, 和 retain 等),③ 程序員不用關(guān)心對(duì)象釋放的問題, 蘋果推薦在新項(xiàng)目中使用 ARC, 但在 iOS5之前的系統(tǒng)中不能采用 ARC.
3>GC① 在Objective_C2.0之后, 內(nèi)存管理出現(xiàn)了類似于 Java 和 C#的內(nèi)存垃圾收集技術(shù), 但是垃圾收集與 ARC 一直運(yùn)行, 垃圾收集是后臺(tái)有一個(gè)線程負(fù)責(zé)檢查已經(jīng)不再使用的對(duì)象,然后釋放之.② 由于后臺(tái)有一個(gè)線程一直運(yùn)行, 一次會(huì)嚴(yán)重影響性能, 這也是 Java 和 C#程序的運(yùn)行速度無法超越 C++的主要原因.③ GC 技術(shù)不能應(yīng)用于 iOS 開發(fā), 只能應(yīng)用于Mac OS X 開發(fā).
小結(jié):從上面的介紹可知:
① iOS 采用 MRR 和 ARC 這兩種方式, ARC 是蘋果推薦的方式.
② MRR 方式相對(duì)比較原始, 對(duì)于程序員的能力要求很高, 但是它很靈活, 方便, 很不容易駕馭好.
二、內(nèi)存泄露
1> 什莫是內(nèi)存泄露?內(nèi)存泄露指當(dāng)一個(gè)對(duì)象或變量在使用完成后沒有釋放掉, 這個(gè)對(duì)象一直占用著這部分內(nèi)存, 直到應(yīng)用停止.
2> 這種沒有 釋放掉的對(duì)象 多了會(huì)發(fā)生什么呢?如果這種對(duì)象過多,內(nèi)存就會(huì)耗盡,其他應(yīng)用就無法運(yùn)行.
3> 在哪里比較普遍?這個(gè)問題在 C++, C 和 Objective-C的 MRR 中是比較普遍的問題.
4> 理論與實(shí)際?從理論上講, 內(nèi)存泄露是由對(duì)象或變量沒有釋放引起的, 但實(shí)踐證明并非所有的未釋放的對(duì)象或變量都會(huì)導(dǎo)致內(nèi)存泄露, 這與硬件環(huán)境和操作系統(tǒng)系統(tǒng)環(huán)境有關(guān)。
5> 我們?cè)撛趺崔k呢?我們需要檢測(cè)工具幫助我們找到這些"泄漏點(diǎn)".
6> 為什么要測(cè)試代碼的內(nèi)存泄露?內(nèi)存的泄露導(dǎo)致我們軟件在運(yùn)行過程中占用越來越多的內(nèi)存,占有資源卻又得不到及時(shí)清理,會(huì)導(dǎo)致我們程序效率越來越低,反應(yīng)慢,會(huì)影響我們用戶體驗(yàn),失去市場(chǎng)的競(jìng)爭(zhēng)能力.
三、查找泄漏點(diǎn) (兩種工具)
在 Xcode 中, 共提供了兩種工具幫助查找泄漏點(diǎn)1 > Analyze
- 學(xué) 名: 靜態(tài)分析工具- 查 找: 可以通過 Product ->Analyze 菜單項(xiàng)啟動(dòng)- 快捷鍵: CMD+shift +b.- Analyze主要分析以下四種問題: 1) 邏輯錯(cuò)誤:訪問空指針或未初始化的變量等; 2) 內(nèi)存管理錯(cuò)誤:如內(nèi)存泄漏等; 3) 聲明錯(cuò)誤:從未使用過的變量; 4) Api調(diào)用錯(cuò)誤:未包含使用的庫(kù)和框架。
2 >Instruments
- 學(xué) 名: 動(dòng)態(tài)分析工具- 查 找: Product ->Profile 菜單項(xiàng)啟動(dòng)- 快捷鍵: CMD + i.- 簡(jiǎn) 介:它有很多跟蹤模塊可以動(dòng)態(tài)分析和跟蹤內(nèi)存, CPU 和文件系統(tǒng).
四、兩種工具查找漏點(diǎn)版面的介紹
1 > 結(jié)合使用-思路分析:先使用 Analyze 靜態(tài)分析查找可疑泄漏點(diǎn), 再用Instruments動(dòng)態(tài)分析中的 Leaks 和 Allocations 跟蹤模板進(jìn)行動(dòng)態(tài)跟蹤分析, 確認(rèn)這些點(diǎn)是否泄漏, 或者是否有新的泄漏點(diǎn)出現(xiàn)等.
**2 > 使用 靜態(tài)檢測(cè)內(nèi)存泄漏工具 Analyze **在 Analyze 靜態(tài)分析結(jié)果中, 凡是有圖標(biāo)

出現(xiàn)的行都是工具發(fā)現(xiàn)的疑似泄露點(diǎn).

疑似泄漏點(diǎn)所在行
點(diǎn)擊疑似泄漏點(diǎn)行末尾的分叉圖標(biāo),會(huì)展開分析結(jié)果:

展開分析結(jié)果
檢測(cè)完成時(shí)的效果圖如下

小結(jié):這里使用 Analyze 靜態(tài)分析查找出來的泄漏點(diǎn),稱之為"可疑泄漏點(diǎn)".之所以稱之為"可疑泄漏點(diǎn)",是因?yàn)檫@些點(diǎn)未必一定泄露,確認(rèn)這些點(diǎn)是否泄露, 還要通過 Instruments 動(dòng)態(tài)分析工具的 Leaks 和 Allocations 跟蹤模板. Analyze 靜態(tài)分析只是一個(gè)理論上的預(yù)測(cè)過程.
3 > 動(dòng)態(tài)監(jiān)測(cè)Instruments的Leaks 1) CMD + i 打開

2) 打開界面的介紹:

在 instruments 中,雖然選擇了 Leaks 模板,但默認(rèn)情況下也會(huì)添加 Allocations 模板.基本上凡是內(nèi)存分析都會(huì)使用 Allocations 模板, 它可以監(jiān)控內(nèi)存分布情況。
① 選中 Allocations 模板,(圖1區(qū)域),右邊的3區(qū)域會(huì)顯示隨著時(shí)間的變化內(nèi)存使用的折線圖,同時(shí)在4區(qū)域會(huì)顯示內(nèi)存使用的詳細(xì)信息,以及對(duì)象分配情況.
② 點(diǎn)擊 Leaks 模板(圖中2區(qū)域), 可以查看內(nèi)存泄露情況。如果在3區(qū)域有 紅X 出現(xiàn), 則有內(nèi)存泄露, 4區(qū)域則會(huì)顯示泄露的對(duì)象.
3) 打用leaks進(jìn)行監(jiān)測(cè):點(diǎn)擊泄露對(duì)象可以在(下圖)看到它們的內(nèi)存地址, 占用字節(jié), 所屬框架和響應(yīng)方法等信息.打開擴(kuò)展視圖, 可以看到右邊的跟蹤堆棧信息

4) 監(jiān)測(cè)結(jié)果的分析:

**4 > Allocations—內(nèi)存分配版面的介紹 **Allocations是檢測(cè)程序運(yùn)行過程中的內(nèi)存分配情況的,也需要同時(shí)運(yùn)行著程序。界面情況如下:

Allocations

Allocations
雙擊某一個(gè)方法,同樣會(huì)跳轉(zhuǎn)到代碼里,會(huì)有每一句代碼對(duì)應(yīng)的內(nèi)存分配情況,根據(jù)這些信息,可以對(duì)程序里不同代碼的內(nèi)存占用情況有一些認(rèn)識(shí),并進(jìn)行針對(duì)性的優(yōu)化。
五、具體使用
1>.Allocations紀(jì)錄了內(nèi)存分配,用來優(yōu)化內(nèi)存使用的2>.Leaks用來分析內(nèi)存泄漏。ARC中引起的內(nèi)存泄漏原因就是引用環(huán)。
第一步先選擇Leaks和Leaks by Backtrace.這里可以看到那些對(duì)象內(nèi)存泄漏了,泄漏了多少,這個(gè)就是簡(jiǎn)單看看,沒有太多調(diào)試意義。

第二步然后看看Call Tree,因?yàn)镃all Tree會(huì)給我們大概的位置,有時(shí)候會(huì)給我們精確的位置,不過要看運(yùn)氣了。然后,再右面選擇Invert Call Tree和Hide System Library

然后雙擊 上文圖片中的任意一行,就會(huì)跳到代碼處內(nèi)存泄漏的地方(事實(shí)上,到這步,很多內(nèi)存泄漏的問題都會(huì)被發(fā)現(xiàn)),當(dāng)然也有一些泄露還是看不出來的.第三步然后我們選擇對(duì)ARC調(diào)試很有用的一個(gè)部分Circles & Roots,通過這個(gè)我們可以看到詳細(xì)的ARC引用計(jì)數(shù)過程。然后,我們看到如下圖
小的紅色矩形點(diǎn)擊可以看到引用計(jì)數(shù)的詳細(xì)信息(ARC 就是自動(dòng)引用計(jì)數(shù),計(jì)數(shù)為0,則對(duì)象會(huì)被釋放)
大的紅色矩形可以繪制對(duì)象引用環(huán)的圖,這里如果是我們自己的東西,就能看出來各個(gè)對(duì)象之間的引用.

如果這里沒有引用環(huán)的圖. 首先我們找一下我們自定義的對(duì)象,正常完成任務(wù)這個(gè)對(duì)就應(yīng)該釋放的. 為了確認(rèn)這個(gè)對(duì)象有沒有釋放, 可以重寫 dealloc 方法, 在此方法中 log 釋放信號(hào), 看看是否被釋放.
如果這里就是沒有釋放,我們可以點(diǎn)擊這兒對(duì)象后的箭頭詳細(xì)的看下, 這個(gè)對(duì)象的引用計(jì)數(shù)變化如圖.
- All 表示所有的引用計(jì)數(shù)變化 - Unpaired表示那些為成對(duì)的變化``(成對(duì)就是leaks識(shí)別出了對(duì)應(yīng)的+1,-1) - By Group會(huì)把相關(guān)的變化分成一組, - ByTime會(huì)按照順序列出引用計(jì)數(shù)變化

我們選擇Unpaired 和 ByGroup,看到如圖

按照順序看(最左邊的標(biāo)號(hào))4 這里,引用計(jì)數(shù)是一,這是正確的,因?yàn)榈竭@里正常就是應(yīng)該是OperationQueue保存一個(gè)Operation的引用。 于是,我們把正常的劃掉

再繼續(xù)看,download start 標(biāo)號(hào)6和8是對(duì)應(yīng)的,繼續(xù)排除問題出現(xiàn)在這里(當(dāng)然問題不可能出現(xiàn)在這里,這是系統(tǒng)的API,一定會(huì)釋放,就是簡(jiǎn)單教大家如何看)

再看看,+1的還剩下標(biāo)號(hào)7 和 11,7 是正常的為Operation分配線程,應(yīng)當(dāng)會(huì)+1,而11就是我們的問題所在了(大部分Delegate都不會(huì)使引用+1)。 我們?cè)倏聪挛臋n
@property(readonly, retain) id< NSURLSessionDelegate > delegate
原來這個(gè)代理是retain啊,不是assign或者weak。所以形成了這樣的引用環(huán)。

那么怎么辦呢?有問題下看文檔,我們看到圖片中引起引用計(jì)數(shù)加一的是
- (NSURLSession *)sessionWithConfiguration:(NSURLSessionConfiguration *)configuration delegate:(id<NSURLSessionDelegate>)delegate delegateQueue:(NSOperationQueue *)queue:
看下文檔,發(fā)現(xiàn)了這個(gè)地方

于是,我們要手動(dòng)的去斷開強(qiáng)引用,于是,我們手動(dòng)去斷開-(void)setOperationFinished { [self.session invalidateAndCancel]; }
再運(yùn)行下看看,能夠正常的Dealloc了.
總結(jié):其實(shí)大多數(shù)問題在雙擊上文的代碼部分就可以解決了,少數(shù)問題需要詳細(xì)的分析ARC引用過程。
建議如果我們未發(fā)現(xiàn)表示內(nèi)存泄露的紅 X, 但是我們想進(jìn)一步評(píng)估某個(gè)對(duì)象對(duì)于內(nèi)存的應(yīng)用, 可以看看 Allocations 模板的折線圖. 反復(fù)執(zhí)行從創(chuàng)建對(duì)象 -> 銷毀對(duì)象 這個(gè)過程, 如果總占用內(nèi)存數(shù)會(huì)隨之增加, 這說明這個(gè)對(duì)象沒有釋放, 有些時(shí)候雖然占用的內(nèi)存不是很嚴(yán)重, 但是也會(huì)增加占用內(nèi)存, 因此必須釋放這個(gè)對(duì)象.
提示:有些情況下, 對(duì)象沒有釋放是無法檢測(cè)到的,反復(fù)測(cè)試內(nèi)存占用也沒有明顯的增加, 這時(shí)最好在配置比較低的設(shè)備上測(cè)試一下, 如果問題依然, 可以不用釋放對(duì)象. 但是從編程習(xí)慣上講, 我們應(yīng)該釋放該對(duì)象.
事實(shí)上,內(nèi)存泄露是及其復(fù)雜的問題, 工具使用是一方面, 經(jīng)驗(yàn)是另一方面. 提高經(jīng)驗(yàn), 然后借助工具才能解決內(nèi)存泄露的根本.
擴(kuò)展小知識(shí)1.什莫是內(nèi)存溢出 out of memory ?是指程序在申請(qǐng)內(nèi)存時(shí),沒有足夠的內(nèi)存空間供其使用,出現(xiàn)out of memory;比如申請(qǐng)了一個(gè)integer,但給它存了long才能存下的數(shù),那就是內(nèi)存溢出。
學(xué)習(xí)文章出處:
1. iOS_ 性能優(yōu)化_內(nèi)存優(yōu)化_Leaks工具的使用
2. [學(xué)習(xí)筆記]_ios內(nèi)存優(yōu)化leaks以及timeProfiler工具的使用