iOS效能優化之記憶體管理:Analyze、Leaks、Allocations的使用和案例程式碼
阿新 • • 發佈:2019-01-23
一. 一些相關概念
1.記憶體空間的劃分: 我們知道,一個程序佔用的記憶體空間,包含5種不同的資料區:(1)BSS段:通常是存放未初始化的全域性變數;(2)資料段:通常是存放已初始化的全域性變數。(3)程式碼段:通常是存放程式執行程式碼。(4)堆:通常是用於存放程序執行中被動態分配的記憶體段,OC物件(所有繼承自NSObject的物件)就存放在堆裡。(5)棧:由編譯器自動分配釋放,存放函式的引數值,區域性變數等值。
棧記憶體是系統來管理的,因此我們常說的記憶體管理,指的是堆記憶體的管理,也就是所有OC物件的建立和銷燬的管理。伴隨著iOS5的到來,蘋果推出了ARC(自動引用計數)技術,此模式下編譯器會自動在合適的地方插入retain、release、autorelease語句,也就是說編譯器會自動生成記憶體管理的程式碼,解放了廣大程式猿的雙手,也基本上避免了記憶體洩露問題,但是呢...
2.記憶體洩露:百度百科給的定義是:"記憶體洩漏也稱作'儲存滲漏',用動態儲存分配函式動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該記憶體單元。直到程式結束。(其實說白了就是該記憶體空間使用完畢之後未回收)即所謂記憶體洩漏"。在iOS應用中的記憶體洩露,原因一般有迴圈引用、錯用Strong/copy等。
二. Analyze—靜態分析
顧名思義,靜態分析不需要執行程式,就能檢查到存在記憶體洩露的地方。
1. 使用方法:開啟Xcode,command + shift + B;或者Xcode - Product - Analyze;
2. 常見的三種洩露情形:
(1)建立了一個物件,但是並沒有使用。Xcode提示資訊:Value Stored to 'number' is never read。翻譯一下:儲存在'number'裡的值從未被讀取過。
(2)建立了一個(指標可變的)物件,且初始化了,但是初始化的值一直沒讀取過。Xcode提示資訊:Value Stored to 'str' during its initialization is never read
(3)呼叫了讓某個物件引用計數加1的函式,但沒有呼叫相應讓其引用計數減1的函式。Xcode提示資訊:Potential leak of an object stored into 'subImageRef'。 翻譯一下:subImageRef物件的記憶體單元有潛在的洩露風險。
3. 貼上Demo程式碼:
**
* 情 形 一:建立了一個物件,但是並沒有使用。
* 提示資訊:Value Stored to 'number' is never read
* 翻譯一下:儲存在'number'裡的值從未被讀取過,
*/
- (void)leakOne {
NSString *str1 = [NSString string];
NSNumber *number;
number = @(str1.length);
/*
說我們沒有讀取過它,那就讀取一下,比如開啟下面這句程式碼,對它傳送class訊息,就不再會有這個提示了。
當然最好的方法還是將有關number的程式碼都刪掉,因為,你只對number賦值,又不使用,那幹嘛創建出來呢。
這是一個比較常見和典型的錯誤,也很容易檢查出來
*/
// [number class];
}
/**
* 情 形 二:建立了一個(指標可變的)物件,且初始化了,但是初始化的值一直沒讀取過。
* 提示資訊:Value Stored to 'str' during its initialization is never read
*/
- (void)leakTwo {
NSString *str = [NSString string]; // 建立並初始化str,此時已經有一個記憶體單元儲存str初始化的值
// NSString *str; // 這樣就記憶體不洩露,因為str是可變的,只需要先宣告就行。
// printf("str前 = %p\n",str);
str = @"ceshi"; // str被改變了,指向了"ceshi"所在的地址,指標改變了,但之前儲存初始化值的記憶體空間還未釋放,儲存str初始化值的記憶體單元洩露了。
// printf("str後 = %p\n",str); // 指標改變了
[str class];
// 再舉兩個例子,同理
NSArray *arr = [NSArray array];
// printf("arr前 = %p\n",arr);
// NSArray *arr; // 這樣就記憶體不洩露
arr = @[@"1",@"2"];
// printf("arr後 = %p\n",arr); // 指標改變了
[arr class];
CGRect rect = self.view.frame;
// CGRect rect = CGRectZero; // 這樣就記憶體不洩露
rect = CGRectMake(0, 0, 0, 0);
NSLog(@"rect = %@",NSStringFromCGRect(rect));
}
/**
* 情 形 三:呼叫了讓某個物件引用計數加1的函式,但沒有呼叫相應讓其引用計數減1的函式。
* 提示資訊:Potential leak of an object stored into 'subImageRef'
* 翻譯一下:subImageRef物件的記憶體單元有潛在的洩露風險
*/
- (void)leakThree {
CGRect rect = CGRectMake(0, 0, 50, 50);
UIImage *image;
CGImageRef subImageRef = CGImageCreateWithImageInRect(image.CGImage, rect); // subImageRef 引用計數 + 1;
UIImage* smallImage = [UIImage imageWithCGImage:subImageRef];
// 應該呼叫對應的函式,讓subImageRef的引用計數減1,就不會洩露了
// CGImageRelease(subImageRef);
[smallImage class];
UIGraphicsEndImageContext();
}
三. Leaks—記憶體洩露
Leaks是動態的記憶體洩露檢查工具,需要一邊執行程式,一邊檢測。
1.使用方法: 進入Xcode,command + control + i;或者Xcode - Xcode - Open Developer Tool - Instruments; 或者Xcode - Product - Profile。選擇Leaks。
2.介面詳情如下,這是執行時的介面
測試了好幾個專案,發現用靜態分析檢查過的程式碼,記憶體洩露都比較少。有2個專案能點的按鈕都點了,能進的頁面都進的,Leaks也沒檢測到洩露。
四. Allocations—記憶體分配
Allocations是檢測程式執行過程中的記憶體分配情況的,也需要同時執行著程式。1.開啟方法:同上。2.介面情況如下:
截圖二:
雙擊某一個方法,同樣會跳轉到程式碼裡,會有每一句程式碼對應的記憶體分配情況,根據這些資訊,可以對程式裡不同程式碼的記憶體佔用情況有一些認識,並進行鍼對性的優化。
五. 平時寫程式碼的一些tip
說到底呢,instruments只是一組工具,幫助我們分析程式碼的工具,可能檢查的出的記憶體問題和效能問題,肯定還是由程式碼造成的。養成良好的程式碼習慣,才是根本的解決方法。首先是避免出現靜態分析裡提到的三種常見記憶體洩露問題,我測試的好幾個專案裡,都有出現這個問題。
tip1:哪些情況會增加App的記憶體佔用?「非完整版」
(1) 建立物件,定義變數。(2)呼叫一個函式或方法。
tip2:哪些情況會增加CPU的消耗?
(1) 建立物件、調整物件屬性、銷燬物件。(2)佈局計算和Autolayout。(3)文字的計算和渲染。(4)圖片的解碼和繪製。「用Time Profiler分析一下,可以更直觀地感受到哪些操作比較耗時,使用方法同上。」
小結:做好cell等可複用物件的重用;可以只建立一次的物件,不要建立多次(比如頁面的某個功能彈窗);用較少的物件和方法呼叫去實現功能;將耗時的操作放在子執行緒等可以對記憶體和效能做一些優化。
1.記憶體空間的劃分: 我們知道,一個程序佔用的記憶體空間,包含5種不同的資料區:(1)BSS段:通常是存放未初始化的全域性變數;(2)資料段:通常是存放已初始化的全域性變數。(3)程式碼段:通常是存放程式執行程式碼。(4)堆:通常是用於存放程序執行中被動態分配的記憶體段,OC物件(所有繼承自NSObject的物件)就存放在堆裡。(5)棧:由編譯器自動分配釋放,存放函式的引數值,區域性變數等值。
棧記憶體是系統來管理的,因此我們常說的記憶體管理,指的是堆記憶體的管理,也就是所有OC物件的建立和銷燬的管理。伴隨著iOS5的到來,蘋果推出了ARC(自動引用計數)技術,此模式下編譯器會自動在合適的地方插入retain、release、autorelease語句,也就是說編譯器會自動生成記憶體管理的程式碼,解放了廣大程式猿的雙手,也基本上避免了記憶體洩露問題,但是呢...
2.記憶體洩露:百度百科給的定義是:"記憶體洩漏也稱作'儲存滲漏',用動態儲存分配函式動態開闢的空間,在使用完畢後未釋放,結果導致一直佔據該記憶體單元。直到程式結束。(其實說白了就是該記憶體空間使用完畢之後未回收)即所謂記憶體洩漏"。在iOS應用中的記憶體洩露,原因一般有迴圈引用、錯用Strong/copy等。
二. Analyze—靜態分析
顧名思義,靜態分析不需要執行程式,就能檢查到存在記憶體洩露的地方。
1. 使用方法:開啟Xcode,command + shift + B;或者Xcode - Product - Analyze;
2. 常見的三種洩露情形:
(1)建立了一個物件,但是並沒有使用。Xcode提示資訊:Value Stored to 'number' is never read。翻譯一下:儲存在'number'裡的值從未被讀取過。
(2)建立了一個(指標可變的)物件,且初始化了,但是初始化的值一直沒讀取過。Xcode提示資訊:Value Stored to 'str' during its initialization is never read
(3)呼叫了讓某個物件引用計數加1的函式,但沒有呼叫相應讓其引用計數減1的函式。Xcode提示資訊:Potential leak of an object stored into 'subImageRef'。 翻譯一下:subImageRef物件的記憶體單元有潛在的洩露風險。
3. 貼上Demo程式碼:
**
* 情 形 一:建立了一個物件,但是並沒有使用。
* 提示資訊:Value Stored to 'number' is never read
* 翻譯一下:儲存在'number'裡的值從未被讀取過,
*/
- (void)leakOne {
NSString *str1 = [NSString string];
NSNumber *number;
number = @(str1.length);
/*
說我們沒有讀取過它,那就讀取一下,比如開啟下面這句程式碼,對它傳送class訊息,就不再會有這個提示了。
當然最好的方法還是將有關number的程式碼都刪掉,因為,你只對number賦值,又不使用,那幹嘛創建出來呢。
這是一個比較常見和典型的錯誤,也很容易檢查出來
*/
// [number class];
}
/**
* 情 形 二:建立了一個(指標可變的)物件,且初始化了,但是初始化的值一直沒讀取過。
* 提示資訊:Value Stored to 'str' during its initialization is never read
*/
- (void)leakTwo {
NSString *str = [NSString string]; // 建立並初始化str,此時已經有一個記憶體單元儲存str初始化的值
// NSString *str; // 這樣就記憶體不洩露,因為str是可變的,只需要先宣告就行。
// printf("str前 = %p\n",str);
str = @"ceshi"; // str被改變了,指向了"ceshi"所在的地址,指標改變了,但之前儲存初始化值的記憶體空間還未釋放,儲存str初始化值的記憶體單元洩露了。
// printf("str後 = %p\n",str); // 指標改變了
[str class];
// 再舉兩個例子,同理
NSArray *arr = [NSArray array];
// printf("arr前 = %p\n",arr);
// NSArray *arr; // 這樣就記憶體不洩露
arr = @[@"1",@"2"];
// printf("arr後 = %p\n",arr); // 指標改變了
[arr class];
CGRect rect = self.view.frame;
// CGRect rect = CGRectZero; // 這樣就記憶體不洩露
rect = CGRectMake(0, 0, 0, 0);
NSLog(@"rect = %@",NSStringFromCGRect(rect));
}
/**
* 情 形 三:呼叫了讓某個物件引用計數加1的函式,但沒有呼叫相應讓其引用計數減1的函式。
* 提示資訊:Potential leak of an object stored into 'subImageRef'
* 翻譯一下:subImageRef物件的記憶體單元有潛在的洩露風險
*/
- (void)leakThree {
CGRect rect = CGRectMake(0, 0, 50, 50);
UIImage *image;
CGImageRef subImageRef = CGImageCreateWithImageInRect(image.CGImage, rect); // subImageRef 引用計數 + 1;
UIImage* smallImage = [UIImage imageWithCGImage:subImageRef];
// 應該呼叫對應的函式,讓subImageRef的引用計數減1,就不會洩露了
// CGImageRelease(subImageRef);
[smallImage class];
UIGraphicsEndImageContext();
}
三. Leaks—記憶體洩露
Leaks是動態的記憶體洩露檢查工具,需要一邊執行程式,一邊檢測。
1.使用方法: 進入Xcode,command + control + i;或者Xcode - Xcode - Open Developer Tool - Instruments; 或者Xcode - Product - Profile。選擇Leaks。
2.介面詳情如下,這是執行時的介面
測試了好幾個專案,發現用靜態分析檢查過的程式碼,記憶體洩露都比較少。有2個專案能點的按鈕都點了,能進的頁面都進的,Leaks也沒檢測到洩露。
四. Allocations—記憶體分配
Allocations是檢測程式執行過程中的記憶體分配情況的,也需要同時執行著程式。1.開啟方法:同上。2.介面情況如下:
截圖二:
雙擊某一個方法,同樣會跳轉到程式碼裡,會有每一句程式碼對應的記憶體分配情況,根據這些資訊,可以對程式裡不同程式碼的記憶體佔用情況有一些認識,並進行鍼對性的優化。
五. 平時寫程式碼的一些tip
說到底呢,instruments只是一組工具,幫助我們分析程式碼的工具,可能檢查的出的記憶體問題和效能問題,肯定還是由程式碼造成的。養成良好的程式碼習慣,才是根本的解決方法。首先是避免出現靜態分析裡提到的三種常見記憶體洩露問題,我測試的好幾個專案裡,都有出現這個問題。
tip1:哪些情況會增加App的記憶體佔用?「非完整版」
(1) 建立物件,定義變數。(2)呼叫一個函式或方法。
tip2:哪些情況會增加CPU的消耗?
(1) 建立物件、調整物件屬性、銷燬物件。(2)佈局計算和Autolayout。(3)文字的計算和渲染。(4)圖片的解碼和繪製。「用Time Profiler分析一下,可以更直觀地感受到哪些操作比較耗時,使用方法同上。」
小結:做好cell等可複用物件的重用;可以只建立一次的物件,不要建立多次(比如頁面的某個功能彈窗);用較少的物件和方法呼叫去實現功能;將耗時的操作放在子執行緒等可以對記憶體和效能做一些優化。