1. 程式人生 > >iOS效能優化之記憶體管理:Analyze、Leaks、Allocations的使用和案例程式碼

iOS效能優化之記憶體管理:Analyze、Leaks、Allocations的使用和案例程式碼

一. 一些相關概念

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等可複用物件的重用;可以只建立一次的物件,不要建立多次(比如頁面的某個功能彈窗);用較少的物件和方法呼叫去實現功能;將耗時的操作放在子執行緒等可以對記憶體和效能做一些優化。