本文章參考自iOS性能分析和優(yōu)化,主要學(xué)習(xí)如何使用性能分析工具Instruments。
一.基本概念
1.內(nèi)存空間的劃分
一個(gè)進(jìn)程占用的內(nèi)存空間,包含5種不同的數(shù)據(jù)區(qū):
(1) BSS段:通常存放未初始化的全局變量
(2)數(shù)據(jù)段:通常存放已初始化的全局變量
(3)代碼段:通常存放程序執(zhí)行代碼
(4)堆:通常存放進(jìn)程運(yùn)行中被動(dòng)態(tài)分配的內(nèi)存段,OC對(duì)象(所有繼承自NSObject的對(duì)象)就存放在堆里。
(5)棧:由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)值,局部變量等值。
其中,棧內(nèi)存是由系統(tǒng)來(lái)管理的,因此我們常說(shuō)的內(nèi)存管理,是指堆內(nèi)存的管理,也就是所有OC對(duì)象的創(chuàng)建和銷毀的管理。隨著蘋果推出了ARC,編譯器會(huì)自動(dòng)生成內(nèi)存管理的代碼,因此我們的內(nèi)存泄漏通常來(lái)源于堆內(nèi)存。
2.內(nèi)存泄漏(Memory Leak)
用動(dòng)態(tài)存儲(chǔ)分配函數(shù)動(dòng)態(tài)開辟的空間,在使用完畢后未釋放,結(jié)果導(dǎo)致一直占據(jù)該內(nèi)存單元,即該內(nèi)存空間使用完畢之后未回收。在ios的內(nèi)存泄漏原因一般有循環(huán)引用、錯(cuò)用Strong/copy等。
二.Instruments多功能檢測(cè)
1.靜態(tài)分析(Analyze)
不需要運(yùn)行程序,就可以檢查到存在內(nèi)存泄漏的地方。

常見(jiàn)的三種泄露情形:
- 創(chuàng)建了一個(gè)對(duì)象但是并沒(méi)有使用,提示:
Value Stored to 'number' is never read - 創(chuàng)建了一個(gè)(指針可變的)對(duì)象,且初始化了,但是初始化的值一直沒(méi)讀取過(guò),提示:
Value Stored to 'str' during its initialization is never read - 調(diào)用了讓某個(gè)對(duì)象引用計(jì)數(shù)加一的函數(shù),但沒(méi)有調(diào)用相應(yīng)的讓其引用計(jì)數(shù)減一的函數(shù),提示:
Potential leak of an object stored into 'subImageRef'
通過(guò)測(cè)試可以發(fā)現(xiàn),程序中有這樣的泄露:

原來(lái)是因?yàn)檫@個(gè)指針初始化之后沒(méi)有使用,其實(shí)在這里如果要賦值的話,直接變成這樣:
NSDictionary *cover = self.covers[indexPath.item];
2.內(nèi)存泄漏檢查工具——Leak
參考Xcode結(jié)合Leaks檢測(cè)內(nèi)存泄露


中途有點(diǎn)報(bào)錯(cuò),關(guān)閉SIP就好了。


哇,嚇我一跳,這么多內(nèi)存泄漏的嗎..趕緊參考了AFNetworking 3.0中調(diào)用[AFHTTPSessionManager manager]方法導(dǎo)致內(nèi)存泄漏的解決辦法,發(fā)現(xiàn)問(wèn)題在于,由于ARC的機(jī)制,導(dǎo)致每當(dāng)實(shí)例化
Session類后都沒(méi)有地方釋放掉實(shí)例,需要把Session類的實(shí)例都改成單例模式。
(附)科普:?jiǎn)卫J?/h5>
1.概念
一個(gè)類只允許有一個(gè)實(shí)例,在整個(gè)程序中需要多次使用,共享同一份資源的時(shí)候,就可以創(chuàng)建單例,一般封裝成工具類使用,如UIApplication,NSUserDefaults,NSNotificationCenter,NSFileManager等。
2.單例優(yōu)缺點(diǎn)
優(yōu)點(diǎn):?jiǎn)卫J綍?huì)使類只有一個(gè)實(shí)例,所以方便使用,并且節(jié)省內(nèi)存資源的分配。因?yàn)槭褂肎CD的方式是線程安全的,所以會(huì)避免資源的多重使用。
缺點(diǎn):?jiǎn)卫齽?chuàng)建的內(nèi)存只有在程序結(jié)束的時(shí)候才會(huì)被釋放。由于單例不能被繼承(因?yàn)榉祷氐氖峭粋€(gè)實(shí)例),所以擴(kuò)展性很不好。
由此,我們首先建立一個(gè)AFSessionSingleton文件,里面包含的是單例模式的聲明。
#import "AFSessionSingleton.h"
@implementation AFSessionSingleton
static AFHTTPSessionManager *manager;
+ (AFHTTPSessionManager *) sharedHttpSessionManager
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
manager = [AFHTTPSessionManager manager];
manager.requestSerializer.timeoutInterval = 10.0;
});
return manager;
}
@end
在調(diào)用的時(shí)候變成這一句:
// AFHTTPSessionManager *session = [AFHTTPSessionManager manager];
AFHTTPSessionManager *session = [AFSessionSingleton sharedHttpSessionManager];
就可以用單例模式避免內(nèi)存泄漏了!
其他問(wèn)題:還有一個(gè)Leak,但是通過(guò)debug發(fā)現(xiàn)沒(méi)什么事,就不管了。


3.自己制造一個(gè)Leak出來(lái)
參考iOS開發(fā)中本人或同事碰到的內(nèi)存泄漏及解決辦法
循環(huán)引用
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSMutableArray *arr1 = [NSMutableArray array];
NSMutableArray *arr2 = [NSMutableArray array];
[arr1 addObject: arr2];
[arr2 addObject: arr1];
}

他說(shuō)的那種方法還是會(huì)導(dǎo)致內(nèi)存泄漏啊..以后還是盡量避免循環(huán)引用吧。
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSMutableArray *arr1 = [NSMutableArray array];
NSMutableArray *arr2 = [NSMutableArray array];
__weak typeof (arr1) weakArr1 = arr1;
[weakArr1 addObject: arr2];
[arr2 addObject: weakArr1];
}

4.Allocations——內(nèi)存分配
用于檢測(cè)程序運(yùn)行過(guò)程中的內(nèi)存分配情況。

5.Time Profiler
分析代碼的執(zhí)行時(shí)間,以便找到導(dǎo)致程序變慢的原因。
當(dāng)點(diǎn)擊Time Profiler應(yīng)用程序開始運(yùn)行后.就能獲取到整個(gè)應(yīng)用程序運(yùn)行消耗時(shí)間分布和百分比.為了保證數(shù)據(jù)分析在統(tǒng)一使用場(chǎng)景真實(shí)行有如下點(diǎn)需要注意:
在開始進(jìn)行應(yīng)用程序性能分析前,請(qǐng)一定要使用真機(jī),因?yàn)槟M器運(yùn)行在Mac上,然而Mac上的CPU往往比iOS設(shè)備要快。相反,Mac上的GPU和iOS設(shè)備的完全不一樣,模擬器不得已要在軟件層面(CPU)模擬設(shè)備的GPU,這意味著GPU相關(guān)的操作在模擬器上運(yùn)行的更慢,尤其是使用CAEAGLLayer來(lái)寫一些OpenGL的代碼時(shí)候。這就導(dǎo)致模擬器性能數(shù)據(jù)和用戶真機(jī)使用性能數(shù)據(jù)相去甚遠(yuǎn)。
可以看到,加載視頻是最花時(shí)間的,然后就是加載瀑布流圖片。

三.一些總結(jié)
1.以下情況會(huì)增加APP的內(nèi)存占用
- 創(chuàng)建對(duì)象,定義變量。
- 調(diào)用一個(gè)函數(shù)或方法。
2.以下情況會(huì)增加CPU的消耗
- 創(chuàng)建對(duì)象、調(diào)整對(duì)象屬性、銷毀對(duì)象。
- 布局計(jì)算和AutoLayout。
- 文本的計(jì)算和渲染。
- 圖片的解碼和繪制。
3.如何避免內(nèi)存泄漏
- 做好cell等可復(fù)用對(duì)象的重用
- 可以只創(chuàng)建一次的對(duì)象,不要?jiǎng)?chuàng)建多次(如頁(yè)面的某個(gè)功能彈窗)
- 用較少的對(duì)象和方法調(diào)用去實(shí)現(xiàn)功能
- 將耗時(shí)的操作放在子線程
4.一些關(guān)于ViewController的內(nèi)存泄漏
參考iOS學(xué)習(xí)——內(nèi)存泄漏檢查及原因分析
在目前主要以ARC進(jìn)行內(nèi)存管理的開發(fā)模式,導(dǎo)致內(nèi)存泄漏的根本原因是代碼中存在循環(huán)引用,從而導(dǎo)致一些內(nèi)存無(wú)法釋放,這就會(huì)導(dǎo)致dealloc()方法無(wú)法被調(diào)用。主要原因大概有一下幾種類型。
1) ViewController中存在NSTimer
NSTimer scheduledTimerWithTimeInterval:1.0
target:self
selector:@selector(updateTime:)
userInfo:nil
repeats:YES];
如果你的ViewController中有NSTimer,那么你就要注意了,因?yàn)楫?dāng)你調(diào)用target:self時(shí),就會(huì)增加ViewController的return count,如果不將這個(gè)Timer invalidate,將不會(huì)調(diào)用dealloc。
2) ViewController中的代理delegate
一個(gè)比較隱秘的因素,要找一下這個(gè)類有關(guān)的代理,看看有沒(méi)有強(qiáng)引用屬性,如果這個(gè)View Controller需要外部傳入某個(gè)Delegate進(jìn)來(lái),來(lái)通過(guò)Delegate+protocol的方式傳參數(shù)給其他對(duì)象,那么這個(gè)delegate一定不要強(qiáng)引用,盡量assign或者weak,否則整個(gè)View Controller會(huì)持續(xù)持有這個(gè)delegate,直到自身被釋放。
3) ViewController中的Block
這個(gè)問(wèn)題會(huì)比較容易犯,Block體內(nèi)使用實(shí)例變量也會(huì)造成循環(huán)引用,使得擁有這個(gè)實(shí)例的對(duì)象不能釋放。因?yàn)樵?code>Block本來(lái)就是當(dāng)前View Controller的一部分,現(xiàn)在Block又強(qiáng)引用self,導(dǎo)致循環(huán)引用無(wú)法釋放。
例如:有一個(gè)類叫OneViewController,有個(gè)屬性是NSString *name,如果在block體內(nèi)使用了self.name或者_name,這個(gè)類就無(wú)法釋放了。
解決方法就是在Block之前聲明當(dāng)前的self為弱引用。
__weak ViewController *weasSelf = self;
4) ViewController的子視圖對(duì)self的持有
我們有時(shí)候需要在自視圖或者某個(gè)cell中點(diǎn)擊跳轉(zhuǎn)等操作,需要在自視圖或者cell中持有當(dāng)前的ViewController對(duì)象,這樣跳轉(zhuǎn)之后的back鍵才能直接返回該頁(yè)面,同時(shí)不銷毀當(dāng)前的ViewController。此時(shí),就需要注意在子視圖或者cell中對(duì)當(dāng)前頁(yè)面的持有對(duì)象不能是強(qiáng)引用,盡量用assign或者weak,否則會(huì)造成循環(huán)引用,導(dǎo)致內(nèi)存無(wú)法釋放。