iOS性能優化之內存管理:Analyze、Leaks、Allocations的使用和案例代碼


 

最近接了個小任務,和公司的iOS小伙伴們分享下instruments的具體使用,於是有了這篇博客...性能優化是一個很大的話題,這里討論的主要是內存泄露部分。

 

一. 一些相關概念

很多人應該比較了解這塊內容了...可以權當復習復習...

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();
}
View Code

貼上照片:

 

三. 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只是一組工具,幫助我們分析代碼的工具,可能檢查的出的內存問題和性能問題,肯定還是由代碼造成的。養成良好的代碼習慣,才是根本的解決方法。首先是避免出現靜態分析里提到的三種常見內存泄露問題,我測試的好幾個項目里,都有出現這個問題。

tip:哪些情況會增加CPU的消耗?「搬運自YYKit的作者的博客(強烈推薦):ibireme

(1) 創建對象、調整對象屬性、銷毀對象。(2)布局計算和Autolayout。(3)文本的計算和渲染。(4)圖片的解碼和繪制。「用Time Profiler分析一下,可以更直觀地感受到哪些操作比較耗時,使用方法同上。」

小結:做好cell等可復用對象的重用;可以只創建一次的對象,不要創建多次(比如頁面的某個功能彈窗);用較少的對象和方法調用去實現功能;將耗時的操作放在子線程等可以對內存和性能做一些優化

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM