2018-06-29 學(xué)習(xí)iOS性能分析和優(yōu)化

本文章參考自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)的三種泄露情形:

  1. 創(chuàng)建了一個(gè)對(duì)象但是并沒(méi)有使用,提示:Value Stored to 'number' is never read
  2. 創(chuàng)建了一個(gè)(指針可變的)對(duì)象,且初始化了,但是初始化的值一直沒(méi)讀取過(guò),提示:Value Stored to 'str' during its initialization is never read
  3. 調(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>

參考iOS-單例模式簡(jiǎn)單使用

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)存占用

  1. 創(chuàng)建對(duì)象,定義變量。
  2. 調(diào)用一個(gè)函數(shù)或方法。

2.以下情況會(huì)增加CPU的消耗

  1. 創(chuàng)建對(duì)象、調(diào)整對(duì)象屬性、銷毀對(duì)象。
  2. 布局計(jì)算和AutoLayout。
  3. 文本的計(jì)算和渲染。
  4. 圖片的解碼和繪制。

3.如何避免內(nèi)存泄漏

  1. 做好cell等可復(fù)用對(duì)象的重用
  2. 可以只創(chuàng)建一次的對(duì)象,不要?jiǎng)?chuàng)建多次(如頁(yè)面的某個(gè)功能彈窗)
  3. 用較少的對(duì)象和方法調(diào)用去實(shí)現(xiàn)功能
  4. 將耗時(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ì)增加ViewControllerreturn 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ú)法釋放。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時(shí)...
    歐辰_OSR閱讀 30,215評(píng)論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,631評(píng)論 1 32
  • 內(nèi)存管理 簡(jiǎn)述OC中內(nèi)存管理機(jī)制。與retain配對(duì)使用的方法是dealloc還是release,為什么?需要與a...
    丶逐漸閱讀 2,080評(píng)論 1 16
  • 前情回顧:渣男日記(十一) “你們知道嗎?世界上百分之八十的財(cái)富,被百分之二十的人占有,同樣的,百分之八十的女人啊...
    聽(tīng)任蔓草湮路閱讀 928評(píng)論 4 2
  • 最近考慮我司品牌定位的一些事情,看到友人朋友圈的書單,閱讀了此書。全書通過(guò)通俗的語(yǔ)言和生動(dòng)的案例,從品牌角度分析了...
    LukeYU閱讀 267評(píng)論 0 1

友情鏈接更多精彩內(nèi)容