iOS 性能調試

性能調優的方式:

 一、經過專門的性能調優工具javascript

 二、經過代碼優化html

1. 性能調優工具:

下面針對iOS的性能調優工具進行一個介紹:java

1.1 靜態分析工具–Analyze

相信iOS開發者在App進行Build或Archive時,會產生不少編譯警告,這些警告是編譯時產生的,靜態分析的過程也相似,在XCode Product菜單下,點擊Analyze對App進行靜態分析。git

Analyze主要分析如下四種問題:程序員

   一、邏輯錯誤:訪問空指針或未初始化的變量等;github

  二、內存管理錯誤:如內存泄漏;web

  三、聲明錯誤:從未使用的變量;數據庫

  四、API調用錯誤:未包含使用的庫和框架。編程

1.2 內存泄漏分析工具–Leaks

點擊XCode的Product菜單Profile啓動Instruments,使用Leaks開始動態分析。
選擇Leaks,會自動啓動Leaks工具和IOS模擬器,Leaks啓動後會開始錄製,隨着對模擬器運行的App的操做,能夠在Leaks中查看內存佔用的狀況。瀏覽器

  注:若是項目使用了ARC,隨着操做,不斷地開啓或關閉視圖,內存可能持續上升,但這不必定表示存在內存泄漏,ARC釋放的時機是不固定的。

Leaks頂部分爲兩欄:All Heap & Anonymous VM和Leaks Checks,All Heap & Anonymous VM中的曲線表明內存分配和內存泄漏曲線。

點擊第二欄Leaks Checks展現內存泄漏,進行內存泄漏分析,將光標放置在上圖的小紅叉上可看到leak數量,右下方是leaks調試的選項:

 建議把Snapshot Interval間隔時間設置爲10秒,勾選Automatic Snapshotting,Leaks會自動進行內存捕捉分析。

 在你懷疑有內存泄漏的操做前和操做後,能夠點擊Snapshot Now進行手動捕捉。

Leaked Object的表格中顯示了內存泄漏的類型、數量及內存空間等。

點擊具體的某個內存泄漏對象,在右側Detail窗口中會出現致使泄漏可能的位置,其中黑色頭像表明了最可能的位置。

Leaks已成功找出了友盟相關的函數:

 內存泄漏動態分析技巧

  熟練使用Leaks後會對內存泄漏判斷更準確,在可能致使泄漏的操做裏,多使用Snapshot Now手動捕捉。

  開始時若是設備性能較好,能夠把自動捕捉間隔設置爲5秒鐘。

  使用ARC的項目,通常內存泄漏都是malloc、自定義結構、資源引發的,多注意這些地方進行分析。

開啓ARC後,內存泄漏的緣由

  開啓了ARC並非就不會存在內存問題,蘋果有句名言:ARC is only for NSObject。

  在IOS 中使用malloc分配的內存,ARC是不會處理的,須要本身進行處理。

  例子中的 CGImageRef 也是一個Image的指針,ARC也不會進行處理。

1.3 不合理內存分析工具–Allocation

關於內存的問題,除了內存泄漏之外,還可能存在內存不合理使用的狀況,也會致使IOS內存警告。

內存的不合理使用每每比內存泄漏更難發現,內存泄漏能夠更多借助於工具的判斷,而內存的不合理運用更多須要開發者結合代碼、架構來進行分析。

明確說明一下二者的區別:

  內存泄漏:指內存被分配了,可是程序中已經沒有志向該內存的指針,致使該內存沒法被釋放,一直佔用着內存,產生內存泄漏。

   內存不合理運用:蘋果官方稱這種狀況爲abandoned memory,也就是存在已分配內存的引用,但實際上程序中並不會使用,好比圖片等對象進行了緩存,可是緩存中的對象一直沒有被使用。

XCode提供的Instruments中的Allocation工具能夠用來幫你瞭解內存的分配狀況,當你的App收到內存警告時,首先應該用Allocation進行內存分析,瞭解哪些對象佔用了太多內存。

1.4 幹掉殭屍對象–Zombies

殭屍對象,也就是咱們會遇到的EXC_BAD_ACCESS錯誤,因爲內存已經被釋放,而這個對象仍舊保留這那個壞地址而致使的。
在MRC的開發中,這個錯誤比較常見,ARC下面在使用到C++的代碼也會遇到。
不過這個工具比較簡單,遇到這類錯誤,打開這個位於Instruments下的工具,直接就能幫你定位到,這裏就不贅述了。

1.5 性能提高工具–Time Profile

這篇文章的重中之重!

既然是性能調優,那麼怎麼提高代碼的運行效率其實才是咱們程序員最直接的訴求,而這個工具能夠輔助咱們辦到這件事。

Time Profiler分析原理: 它按照固定的時間間隔來跟蹤每個線程的堆棧信息,經過統計比較時間間隔之間的堆棧狀態,來推算某個方法執行了多久,並得到一個近似值。它將各個方法消耗的時間統計起來,造成了咱們直接定位須要進行優化的代碼的好幫手。

選擇Time Profiler工具開始測試,這時會自動啓動模擬器和Time Profiler錄製。

先進行一些App的操做,讓Time Profiler收集足夠的數據,尤爲是你以爲那些有性能瓶頸的地方。

  2是擴展面板,用來跟蹤顯示堆棧;

  3裏面有設置和詳情,能夠從這裏對錄製作些配置,detail下查看到cpu運行的時間都消耗在哪裏;

 

經過對應用的操做,能夠在詳細面板中看到那些最耗時的操做是哪些,並能夠逐行展開查看:

圖標爲黑色頭像的就是Time Profiler給咱們的提示,有可能存在性能瓶頸的地方,能夠逐漸向下展開,找到產生的根本緣由。

Time Profiler參數設置

這裏邊幾個選項的含義以下:

  Separate by Thread: 每一個線程應該分開考慮。只有這樣你才能揪出那些大量佔用CPU的」重」線程

  Invert Call Tree: 從上倒下跟蹤堆棧,這意味着你看到的表中的方法,將已從第0幀開始取樣,這一般你是想要的,只有這樣你才能看到CPU中話費時間最深的方法.也就是說FuncA{FunB{FunC}} 勾選此項後堆棧以C->B-A 把調用層級最深的C顯示在最外面

  Hide System Libraries: 勾選此項你會顯示你app的代碼,這是很是有用的. 由於一般你只關心cpu花在本身代碼上的時間不是系統上的

  Flatten Recursion: 遞歸函數, 每一個堆棧跟蹤一個條目

  Top Functions: 一個函數花費的時間直接在該函數中的總和,以及在函數調用該函數所花費的時間的總時間。所以,若是函數A調用B,那麼A的時間報告在A花費的時間加上B花費的時間,這很是真有用,由於它可讓你每次下到調用堆棧時挑最大的時間數字,歸零在你最耗時的方法。

上面的參數在實踐中合理設置,也沒有什麼太多技巧,就是經過數據的隱藏、顯示讓咱們更關注於想找到的數據。

2. 性能調優的代碼優化:

下面介紹一下在開發中能夠直接進行的代碼優化的方面。

2.1 views設置爲不透明(opaque=yes)

(opaque)這個屬性給渲染系統提供了一個如何處理這個view的提示。若是設爲YES, 渲染系統就認爲這個view是徹底不透明的,這使得渲染系統優化一些渲染過程和提升性能。

若是設置爲NO,渲染系統正常地和其它內容組成這個View。默認值是YES。

給大家看個圖,大家就知道了,若是這個屬性爲NO,那麼:

這裏寫圖片描述

知道了吧,GPU會利用圖層顏色合成公式去合成真正的色值,這在ScrollView或者動畫中是很消耗性能的。

2.2 不要阻塞主線程

永遠不要使主線程承擔過多。由於UIKit在主線程上作全部工做,渲染,管理觸摸反應,迴應輸入等都須要在它上面完成。

一直使用主線程的風險就是若是你的代碼真的block了主線程,你的app會失去反應。

大部分阻礙主進程的情形是你的app在作一些牽涉到讀寫外部資源的I/O操做,好比存儲或者網絡。

一般建議這些操做都使用GCD的方式直接異步執行,並將UI相關操做在主線程進行回調,像這樣:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    // 切換到全局隊列,異步執行耗時操做
    dispatch_async(dispatch_get_main_queue(), ^{
        // 切換到主線程,更新你的UI。
    });
});

2.3 提早調整ImageView中的圖片大小(同圖片和動畫的渲染)

若是要在UIImageView中顯示一個圖片,你應保證圖片的大小和UIImageView的大小相同。
由於在運行中縮放圖片是很耗費資源的,特別是UIImageView嵌套在UIScrollView中的狀況下。

若是圖片是從遠端服務加載的你不能控制圖片大小,好比在下載前調整到合適大小的話,你能夠在下載完成後,最好是用background thread,縮放一次,而後在UIImageView中使用縮放後的圖片。

這個類比到圖片和動畫的渲染中,是通用的。

具體方法參考上面的GCD操做。

2.4 正確使用容器的特性

Arrays: 有序的一組值。使用index來查找很快,使用value 查找很慢, 插入/刪除很慢。 Dictionaries: 存儲鍵值對。 用鍵來查找比較快。 Sets: 無序的一組值。用值來查找很快,插入/刪除很快。

2.5 大文件傳輸使用gzip

大量app依賴於遠端資源和第三方API,你可能會開發一個須要從遠端下載XML, JSON, HTML或者其它格式的app。

問題是咱們的目標是移動設備,所以你就不能期望網絡情況有多好。一個用戶如今還在edge網絡,下一分鐘可能就切換到了3G。不論什麼場景,你確定不想讓你的用戶等太長時間。

減少文檔的一個方式就是在服務端和你的app中打開gzip。這對於文字這種能有更高壓縮率的數據來講會有更顯著的效用。

固然,如今蘋果已經自動支持了,你只須要告訴大家服務端的同窗,傳輸大文件的時候記得用gzip就完了。

2.6 View的重用和懶加載

更多的view意味着更多的渲染,也就是更多的CPU和內存消耗,對於那種嵌套了不少view在UIScrollView裏邊的app更是如此。

重用就是模仿UITableViewUICollectionView的操做: 不要一次建立全部的subview,而是當須要時才建立,當它們完成了使命,把他們放進一個可重用的隊列中。
當須要使用View的時候,去可重用隊列裏面找一找有沒有能夠被複用的View。
這裏個人一份框架中曾經使用過相似的方法去建立一個圖片瀏覽器,你們能夠稍作參考。View的重用

懶加載就是在程序啓動時並不進行加載,只有當用到這個對象的時候,才進行加載。
這個不只在屬性中能夠進行這樣的使用,在View上面也是同樣,不過實現稍有不一樣。
懶加載會消耗更少內存,可是在View的顯示上會稍有滯後。

2.7 Cache

一個極好的原則就是,緩存所須要的,也就是那些不大可能改變可是須要常常讀取的東西。

咱們能緩存些什麼呢?一些選項是,遠端服務器的響應,圖片,甚至計算結果,好比UITableView的行高。

NSURLConnection默認會緩存資源在內存或者存儲中根據它所加載的HTTP Headers。你甚至能夠手動建立一個NSURLRequest而後使它只加載緩存的值。

下面是一個可用的代碼段,你能夠能夠用它去爲一個基本不會改變的圖片建立一個NSURLRequest並緩存它:

+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {

    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
 
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad; // this will make sure the request always returns the cached image
    request.HTTPShouldHandleCookies = NO;
    request.HTTPShouldUsePipelining = YES;
    [request addValue:@"image/*" forHTTPHeaderField:@"Accept"];
 
    return request;
}

注意你能夠經過 NSURLConnection 獲取一個URL request, AFNetworking也同樣的。這樣你就沒必要爲採用這條tip而改變全部的networking代碼了。

若是你須要緩存其它不是HTTP Request的東西,你能夠用NSCache。

2.8 記得處理內存警告

一旦系統內存太低,iOS會通知全部運行中app。在官方文檔中是這樣記述:

  若是你的app收到了內存警告,它就須要儘量釋放更多的內存。最佳方式是移除對緩存,圖片object和其餘一些能夠重建立的objects的strong references.

幸運的是,UIKit提供了幾種收集低內存警告的方法:

  在app delegate中使用<code>applicationDidReceiveMemoryWarning:</code> 的方法 在你的自定義UIViewController的子類(subclass)中覆蓋<code>didReceiveMemoryWarning</code> 註冊並接收 UIApplicationDidReceiveMemoryWarningNotification 的通知

一旦收到這類通知,你就須要釋聽任何沒必要要的內存使用。

  例如,UIViewController的默認行爲是移除一些不可見的view, 它的一些子類則能夠補充這個方法,刪掉一些額外的數據結構。一個有圖片緩存的app能夠移除不在屏幕上顯示的圖片。

這樣對內存警報的處理是很必要的,若不重視,你的app就可能被系統殺掉。

然而,當你必定要確認你所選擇的object是能夠被重現建立的來釋放內存。必定要在開發中用模擬器中的內存提醒模擬去測試一下。

2.9 重用大的開銷對象

這裏的大開銷是指一些初始化很慢的objects,如:NSDateFormatter和NSCalendar。可是,你又不可避免地須要使用它們,好比從JSON或者XML中解析數據。

想要避免使用這個對象的瓶頸你就須要重用他們,能夠經過添加屬性到你的class裏或者建立靜態變量來實現。

注意若是你要選擇第二種方法,對象會在你的app運行時一直存在於內存中,和單例(singleton)很類似。

下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調用時它會建立一個新的實例,之後的調用則將返回已經建立的實例:

// in your .h or inside a class extension

@property (nonatomic, strong) NSDateFormatter *formatter;
 
// inside the implementation (.m)
// When you need, just use self.formatter
- (NSDateFormatter *)formatter {
    if (! _formatter) {
        _formatter = [[NSDateFormatter alloc] init];
        _formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy"; // twitter date format
    }
    return _formatter;
}

還須要注意的是,其實設置一個NSDateFormatter的速度差很少是和建立新的同樣慢的!因此若是你的app須要常常進行日期格式處理的話,你會從這個方法中獲得不小的性能提高。

2.10 避免反覆的處理數據

許多應用須要從服務器加載功能所需的常爲JSON或者XML格式的數據。在服務器端和客戶端使用相同的數據結構很重要。在內存中操做數據使它們知足你的數據結構是開銷很大的。

好比你須要數據來展現一個table view,最好直接從服務器取array結構的數據以免額外的中間數據結構改變。

相似的,若是須要從特定key中取數據,那麼就使用鍵值對的dictionary。

2.11 正確設定背景圖片

在View裏放背景圖片就像不少其它iOS編程同樣有不少方法:

  使用UIColor的 colorWithPatternImage來設置背景色; 在view中添加一個UIImageView做爲一個子View。

若是你使用全畫幅的背景圖,你就必須使用UIImageView由於UIColor的colorWithPatternImage是用來建立小的重複的圖片做爲背景的。這種情形下使用UIImageView能夠節約很多的內存:

// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [ [UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];

若是你用小圖平鋪來建立背景,你就須要用UIColor的colorWithPatternImage來作了,它會更快地渲染也不會花費不少內存:

self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];

2.12 試試蘋果最新的WKWebView來處理web

UIWebView頗有用,用它來展現網頁內容或者建立UIKit很難作到的動畫效果是很簡單的一件事。

可是你可能有注意到UIWebView並不像驅動Safari的那麼快。這是因爲以JIT compilation 爲特點的Webkit的Nitro Engine的限制。

因此想要更高的性能你就要調整下你的HTML了。第一件要作的事就是儘量移除沒必要要的javascript,避免使用過大的框架。能只用原生js就更好了。

另外,儘量異步加載例如用戶行爲統計script這種不影響頁面表達的javascript。

最後,永遠要注意你使用的圖片,保證圖片的符合你使用的大小。使用Sprite sheet提升加載速度和節約內存。

固然,上面是針對你在使用UIWebView的狀況下,須要儘可能減小使用web的特性,而蘋果最近剛推出的Safari的底層框架WKWebView也許能幫咱們規避掉不少這樣的性能問題。

2.13 優化你的TableView

Table view須要有很好的滾動性能,否則用戶會在滾動過程當中發現動畫的瑕疵。

爲了保證table view平滑滾動,確保你採起了如下的措施:

  正確使用reuseIdentifier來重用cells 儘可能使全部的view opaque,包括cell自身 避免漸變,圖片縮放,後臺選人 緩存行高 若是cell內現實的內容來自web,使用異步加載,緩存請求結果 使用shadowPath來畫陰影 減小subviews的數量 儘可能不適用cellForRowAtIndexPath:,若是你須要用到它,只用一次而後緩存結果 使用正確的數據結構來存儲數據 使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight來設定固定的高,不要請求delegate

2.14 選擇正確的數據存儲方式

當存儲大塊數據時你會怎麼作?

你有不少選擇,好比:

  使用NSUerDefaults 使用XML, JSON, 或者 plist 使用NSCoding存檔 使用相似SQLite的本地SQL數據庫 使用 Core Data

NSUserDefaults的問題是什麼?雖然它很nice也很便捷,可是它只適用於小數據,好比一些簡單的布爾型的設置選項,再大點你就要考慮其它方式了

XML這種結構化檔案呢?整體來講,你須要讀取整個文件到內存裏去解析,這樣是很不經濟的。使用SAX又是一個很麻煩的事情。

NSCoding?不幸的是,它也須要讀寫文件,因此也有以上問題。

在這種應用場景下,使用SQLite 或者 Core Data比較好。使用這些技術你用特定的查詢語句就能只加載你須要的對象。

在性能層面來說,SQLite和Core Data是很類似的。他們的不一樣在於具體使用方法。Core Data表明一個對象的graph model,但SQLite就是一個DBMS。Apple在通常狀況下建議使用Core Data,可是若是你有理由不使用它,那麼就去使用更加底層的SQLite吧。

若是你使用SQLite,你能夠用FMDB(https://github.com/ccgus/fmdb)這個庫來簡化SQLite的操做,這樣你就不用花不少經歷瞭解SQLite的C API了。

2.15 把Xib換成Storyboard吧

當你加載一個XIB的時候全部內容都被放在了內存裏,包括任何圖片。若是有一個不會即刻用到的view,你這就是在浪費寶貴的內存資源了。

Storyboards就是另外一碼事兒了,storyboard僅在須要時實例化一個view controller.

當加載XIB時,全部圖片都被緩存,若是你在作OS X開發的話,聲音文件也是。Apple在相關文檔中的記述是:

  當你加載一個引用了圖片或者聲音資源的nib時,nib加載代碼會把圖片和聲音文件寫進內存。在OS X中,圖片和聲音資源被緩存在named cache中以便未來用到時獲取。在iOS中,僅圖片資源會被存進named caches。取決於你所在的平臺,使用NSImage 或UIImage 的`imageNamed:`方法來獲取圖片資源。

很明顯,一樣的事情也發生在storyboards中,但我並無找到任何支持這個結論的文檔。

另外,快速打開app是很重要的,特別是用戶第一次打開它時,對app來說,第一印象太太過重要了。

你能作的就是使它儘量作更多的異步任務,好比加載遠端或者數據庫數據,解析數據。

仍是那句話,避免過於龐大的XIB,由於他們是在主線程上加載的。因此儘可能使用沒有這個問題的Storyboards吧!

  注意,用Xcode debug時watchdog並不運行,必定要把設備從Xcode斷開來測試啓動速度

2.16 學會手動建立Autorelease Pool

NSAutoreleasePool負責釋放block中的autoreleased objects。通常狀況下它會自動被UIKit調用。可是有些情況下你也須要手動去建立它。

假如你建立不少臨時對象,你會發現內存一直在減小直到這些對象被release的時候。這是由於只有當UIKit用光了autorelease pool的時候memory纔會被釋放。

好消息是你能夠在你本身的@autoreleasepool裏建立臨時的對象來避免這個行爲:

NSArray *urls = [@"url1",@"url2"];
for (NSURL *url in urls) {
    @autoreleasepool {
        NSError *error;
        NSString *fileContents = [NSString stringWithContentsOfURL: url encoding: NSUTF8StringEncoding error: &error];
        /* Process the string, creating and autoreleasing more objects. */
    }
}

這段代碼在每次遍歷後釋放全部autorelease對象。

連接

相關文章
相關標籤/搜索