最近接了個小任務,和公司的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(); }
貼上照片:
三. 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等可復用對象的重用;可以只創建一次的對象,不要創建多次(比如頁面的某個功能彈窗);用較少的對象和方法調用去實現功能;將耗時的操作放在子線程等可以對內存和性能做一些優化。