當我們開發iOS應用時,好的性能對我們的App來說是很重要的。你的用戶也希望如此,但是如果你的app表現的反應遲鈍或者很慢也會傷害到你的審核。
然而,由於IOS設備的限制有時很難工作得很正確。我們開發時有很多需要我們記住這些容易忘記的決定對性能的影響。
這是為什麼我寫這篇文章的原因。這篇文章用備忘錄的形式集合了25個技巧和訣竅可以用來提高你的app性能。所以保持閱讀來給你未來的App一個很不錯的提高。
Note:在優化代碼之前,必須保證有個需要解決的問題!不要陷入"pre-optimizing(預優化)"你的代碼。勤 用Instruments分析你的代碼,發現任何一個需要提高的地方。Matt Galloway寫了一個使用Instruments優化代碼的的教程
以下這些技巧分為三個不同那個的級別---基礎,中級,高級。
基礎這些技巧你要總是想著實現在你開發的App中。
1.用ARC去管理內存(Use ARC to Manage Memory)
2.適當的地方使用reuseIdentifier(Use areuseIdentifierWhere Appropriate)
3.盡可能設置視圖為不透明(Set View as Opaque When Possible)
4.避免臃腫的XIBs文件(Avoid Fat XiBs)
5.不要阻塞主進程(Don't Block the Main Thread)
6.調整圖像視圖中的圖像尺寸(Size Images to Image Views)
7.選擇正確集合(Choose the Correct Collection)
8.啟用Gzip壓縮(Enable GZIP Compression)
中級
這些技巧是當你遇到更復雜的情況的時候使用。
9. 重用和延遲加載視圖(Reuse and Lazy Load Views)
10.緩存,緩存,緩存(Cache,Cache,Cache)
11.考慮繪圖(Consider Drawing)
12.處理內存警告(Handle Memory Warnings)
13.重用大開銷對象(Reuse Expensive Objects)
14.使用精靈表(Use Sprite Sheets)
15.避免重復處理數據(Avoid Re-Processing Data)
16.選擇正確的數據格式(Choose the Right Data Format)
17.適當的設置背景圖片(Set Background Images Appropriately)
18.減少你的網絡占用(Reduce Your Web Footprint)
19.設置陰影路徑(Set the Shadow Path)
20.你的表格視圖Optimize Your Table Views)
21.選擇正確的數據存儲方式(Choose Correct Data Storage Option)
高級
這些技巧你應該只在你很積極認為它們能解決這個問題,而且你覺得用它們很舒適的時候使用。
22.加速啟動時間(Speed up Launch Time)
23.使用自動釋放池(Use AutoRelease Pool)
24.緩存圖像(Cache Images-Or not)
25.盡可能避免日期格式化器(Avoid Date Formatters Where Possible)
沒有其他的,一起去看看這些技巧吧!
基礎的性能提升
1)用ARC去管理內存
ARC是伴隨IOS5 一起發布的,它用來消除常見的的內存洩漏。
ARC是"Automatic Reference Counting"的縮寫。它自動管理你代碼中的retain/release循環,這樣你就不必手動做這事兒了。
下面這段代碼展示了創建一個view的常用代碼
查看源碼打印?1
UIView *view =[[UIView alloc] init];
2
//...
3
[self.view addSubview:view];
4
[view release];
這裡極其容易忘記在代碼結束的地方調用release,ARC將會自動的,底層的為你做這些工作。
除了幫助你你避免內存洩漏,ARC還能保證對象不再使用時立馬被回收來提高你的性能。你應該在你的工程裡多用ARC。
這裡是一些學習更多關於ARC的非常棒的資源
Apple’s official documentation 蘋果的官方文檔。 Matthijs Hollemans’sBeginning ARC in iOS TutorialTony Dahbura’sHow To Enable ARC in a Cocos2D 2.X Project 如果你還是不確信ARC的好處,看看這篇文章eight myths about ARC說服你為什麼用ARC。值得注意的是ARC不能消除所有的內存洩漏。你依然有可能內存洩漏,這主要可能是由於blocks(塊),引用循環,CoreFoundation對象管理不善(通常是C結構體,或者是確實很糟糕的代碼)。
2)適當的地方使用reuseIdentifier
在app開發中的一個常見的為UITableViewCells,UICollectionViewCells,UITableViewHeaderFooterViews設置一個正確的reuseIdentifier(重用標識)。
為了最大化性能,一個tableView的數據源一般應該重用UITableViewCell對象,當它在tableView:cellForRowAtIndexPath:中分配數據給cells的時候。一個表視圖維護了一個UITableViewCell對象的隊列或者列表,這些對象已被數據源標記為重用。
如果你不用reuseIdentifier 會怎麼樣呢?如果你用,你的tableview每顯示一行將會配置一個全新的cell。這是非常費事的操作而且絕對會影響你app滾動的性能。
自從引進了iOS6,你應該為header and footer 視圖設置reuseIdentifiers,就像在UICollectionView’s cells 和 supplementary views(補充視圖)一樣。
使用reuseIdentifiers,當你的數據源要求提供一個新的cell給tableview的時候調用這個方
查看源碼打印?1
NSString *CellIdentifier = @"Cell";
2
3
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
3)可能的時候設置視圖為不透明
如果你有不透明視圖(opaque views)--也就是說,沒有透明度定義的視圖,你應該設置他們的opaque屬性為YES。
為什麼? 這會允許系統以最優的方式繪制你的views。這是一個簡單的屬性可以在Interface Builder 和代碼中設置。
蘋果的文檔Apple documentation中有對這個屬性的描述
這個屬性提供了一個提示給圖系統如何對待這個視圖。如果設置為YES,繪制系統將會把這個視圖視為完全不透明。這樣允許系統優化一些繪制操作和提高性能。如果設置為NO,繪圖系統會復合這個視圖和其他的內容,這個屬性的默認值是YES
在相對靜態的屏幕上,設置opaque屬性不會有什麼大問題。盡管如此,如果你的視圖是嵌入在一個scrollView,或者是一個復雜的動畫的一部分,不設置這個屬性絕對會影響你的程序的性能。
你也可以使用Debug\Colorolor Blended Layers選項 在你的模擬器中形象化的看見沒有設置為不透明(opaque)的視圖.你的目標應該是盡可能多的設置視圖為透明。
4)避免臃腫的XIB文件
故事板,由iOS5引進,很快的替代XIBs。盡管如此,XIBs在一下情況下依然是很有用的。如果你需要在IOS5之前版本的設備上運行或者你想自定義重用的視圖,那麼你確實不能避免使用它們。
如果你專注使用XIBs,那麼讓它們盡量的簡單。嘗試為一個試圖控制器創建一個XIB,如果可能的話,把一個視圖控制器的視圖分層管理在單獨的XIBs中。
注意當你加載一個XIB到內存的時候,它所有的內容都會載入內存,包括所有的圖片。如果你有視圖但不是要立即使用,那你就浪費了珍貴的內存。值得注意的是這不會發生在故事板中,因為故事版只會在需要的時候實例化一個視圖控制器。
當你載入一個xib,所有的圖像文件會被緩存,如果是開發OSX,那麼音頻文件也會被緩存。
Apple’s documentation如是說:
當你載入一個包含了圖和聲音資源引用的nib文件時,nib加載代碼讀取實際的圖片文件和音頻文件到內存中並緩存它。在OS X中,圖片和音頻資源被存儲在已命名的緩存 中這樣你可以在之後需要的時候訪問它們。在iOS中,只有圖片資源被緩存,訪問圖片,你使用NSImage或者UIImage的imageNamed:方法來訪問,具體使用取決於你 的平台。
顯然這也發生在使用故事板的時候。盡管如此,我還不能找到這種說法的證據。如果你知道,請給我留言。
想學習更多關於故事板的更多內容嗎?看看Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1andPart 2.
5)不要阻塞主進程
你永遠不應該在主線程中做任何繁重的工作。這是因為UIKIt的所有工作都在主線程中進行,比如繪畫,管理觸摸,和響應輸出。
你的app的所有工作都在主線程上進行就會有阻塞主線程的風險,你的app會表現的反應遲鈍。這是在App Store裡獲一星評論的快速途徑!(作者賣萌..)
阻塞主線程最多的情況就是發生在你的app進行I/O操作,包括牽扯到任何需要讀寫外部資源的任務,比如讀取磁盤或者網絡
你可以異步的執行網絡任務使用NSURLConnection中的這個方法:
查看源碼打印?1
+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void(^)(NSURLResponse*, NSData*, NSError*))handler
或者使用第三方框架比如AFNetworking.
如果你在做任何大開銷的操作(比如執行一個耗時的計算,或者讀寫磁盤)使用Grand Central Dispatch(GCD)或者NSOperations 和 NSOperationQueues.
使用GCD的模板如下代碼所示:
查看源碼打印?01
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
02
03
// switch to a background thread and perform your expensive operation
04
05
06
07
dispatch_async(dispatch_get_main_queue(), ^{
08
09
// switch back to the main thread to update your UI
10
11
12
13
});
14
15
});
這裡為什麼dispatch_async 嵌套在第一個的裡面?這是因為任何UIKit相關的代碼都必須在主線程上執行。
對NSOperation和GCD的詳情感興趣?看看Ray Wenderlich’sMultithreading and Grand Central Dispatch on iOS for Beginners教程,和 Soheil Azarpour’sHow To Use NSOperations and NSOperationQueues教程。
6)調整圖像視圖中的圖像尺寸
如果你用UIImageView呈現app束中的圖片時,確認圖片和UIImageView的尺寸相同。縮放圖片會非常的耗時,特別是當你的UIImageView被嵌入UIScrollView。
如果圖片是從遠程服務器上下載的,有時你沒法控制圖片尺寸,或者你不能在服務器上在下載之前縮放它。在這些情況下你可以在圖片下載完成後手動縮放一次,最好是在後台進程中。然在UIImageView中使用調整尺寸之後的圖片。
7)選擇正確集合
學著怎麼在手頭工作中使用最合適的類或對象是寫出高效代碼的基本。當時用集合是(collections),這個說法特別對。
可喜的是在蘋果開發者文檔(Collections Programming Topics)中有詳細解釋可用類之間的關系,還有解釋各個類的適用情況。這個文檔是每個使用集合的人的必讀文檔。
這是一個最常見的集合類型的快速簡介:
Arrays:有序的值的列表,用index快速查找,通過值查找慢,insert/delete操作慢。 Dictionaries:存儲鍵/值對.用index快速查找。 Sets: 無序的值列表。通過值快速查找,insert/delete快。
8)啟用Gzip壓縮
大量和持續增長的app依賴從遠端服務器或者外部APIs獲取的外部數據。某些時候你可能會開發一些需要下載XML,JSON,HTML或者其他文本格式的應用。
問題是移動設備不能保證網絡環境,用戶可能一分鐘在邊緣網絡,下一分鐘又是3G網絡,無論什麼情況下,你不想你的用戶一直等待。
一個減少文件大小並加速下載的網絡資源的方法是同時在你的服務器和客戶端上使用GZIP壓縮,對於文本數據這種有高比率壓縮的數據來說非常有用。
好消息是iOS早已默認支持GZIP壓縮,如果你是使用NSURLConnection或者建立在這之上的框架比如AFNetworking。更好的消息是一切雲服務提供商像Google App Engine早已發送壓縮之後的響應數據。
這裡有一篇文章great article about GZIP compression介紹如何在你的Apache或IIS服務器上啟用GZIP。
中級性能提升
好的,當談到優化你的代碼時,你應該很自信你已經初級的方法已經完全掌握了。但有時候有的問題的解決方法並不是那麼顯而易見,它由你app的結構和代碼決定,盡管如此,在正確的上下文中,它們可能是沒有價值的。
9)重用和延遲加載視圖
越多的視圖就有越多的繪圖操作,最終意味著更多的CPU和內存開銷。這說得特別對如果你的app嵌入很多視圖在UIScrollView時。
管理這個的技巧是去模擬UITableView 和 UICollectionView的行為:不要一次創建所有的子視圖,而是在需要的時候創建,然後把他們假如重用隊列中。
這樣,你只需要在視圖浮動時配置你的視圖,避免昂貴的資源分配開銷。
視圖創建的時機問題也同樣適用於你app的其他地方。試想當你點擊一個button時呈現一個視圖的情景。至少有兩種方法:
1.屏幕第一次載入時創建視圖並隱藏它。當你需要的時候,顯示出來。
2.需要呈現的時候一次創建視圖並顯示它。
每種方法都有各自的優缺點
使用第一種方法,你消耗了更多內存因為從創建開始到它釋放前你都保持了它的內存,然而,當你點擊button的時候,你的app會表現得響應快速因為它只需要更改視圖的可視化屬性。
使用第二種方法會有相反的效果,在需要的時候創建視圖,消耗更少的內存,但當button被點擊時應用會表現得不那麼響應快速。
10)緩存,緩存,緩存
在開發應用時的一個偉大的經驗是"Cache what matters"--也就是說那些不大會改變但會平凡被訪問的東西。
你能緩存些什麼呢?緩存的候選項有遠程服務器的響應,圖片,已計算過的值(比如UITableView的行高)。
NSURLConnection 根據處理的Http頭緩存資源到磁盤或者內存中,你甚至可以手動創建一個NSURLRequest值加載緩存過的值。
這裡有一段很棒的代碼,用在任何時候你需要針對一個不大會改變的圖片創建一個NSURLRequest。
查看源碼打印?01
+ (NSMutableURLRequest *)imageRequestWithURL:(NSURL *)url {
02
03
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
04
05
06
request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;// this will make sure the request always returns the cached image
07
08
request.HTTPShouldHandleCookies = NO;
09
10
request.HTTPShouldUsePipelining = YES;
11
12
[request addValue:@"image/*"forHTTPHeaderField:@"Accept"];
13
14
returnrequest;
15
16
}
如果想知道更多關於Http caching,NSURLCache,NSURLConnection等內容,請閱讀the NSURLCache entry
注意,你可以通過NSURLConnection獲取取一個URL請求,AFNetworking也可以。有了這個技巧這樣你不用改變任何你的網絡代碼。
如果要緩存不牽扯到HTTP請求的其他東西,NSCache是很好的選擇。
NSCache像NSDictionary,但是當系統需要回收內存的時候會自動的移除內容。
對HTTP Cache感興趣並想學更多的內容?推薦閱讀這篇文章best-practices document on HTTP caching
11)考慮繪圖
在IOS中有很多方法可以制作擁有很棒外觀的buttons,你可以是由全尺寸的圖像,也可以使用調整尺寸之後的圖像,或者你用CALayer,CoreGraphics,甚至OpenGL手動的它們。
當然,每種途徑都有不同的復雜度級別和不同的性能,這篇文章非常值得一讀post about iOS graphics performance here,這是Apple UIKit團隊成員Andy Matuschak發表的文章,裡面對各種方法有一些非常棒的見解和對性能的權衡。
使用預渲染圖片更快,因為iOS不用創建一張圖像和繪制圖形到屏幕上(圖像已經處理好了)。問題是你需要全部把這些圖片放進應用束裡,增加它的尺寸。那就是為什麼使用可調整尺寸的圖片是那麼好:你通過移除”浪費了的“圖片空間來節約空間。你也不需要為不同的元素生成不同的圖片。(例如 buttons)
盡管如此,用圖片你會失去代碼調整你圖片的能力,需要一次又一次的生成它們然後把它們加入到應用中。這是個緩慢的過程。另外一點如果你有動畫或者很多張稍微變化的圖片(例如 顏色疊加),你需要加很多的圖片增加了應用束的大小。
總結一下,你需要想對你來說最重要的是什麼:繪圖性能還是app的大笑.通常兩個都很重要,所以你會在一個工程裡使用這兩種方法。
12)處理內存警告
當系統內存低的時候iOS會通知所有的正在運行的app,關於低內存警告的處理蘋果官方文檔official Apple documentation描述:
如果你的應用收到這個警告,它必須盡可能多的釋放內存。最好的方法是移除對緩存,圖像對象,和其他稍後要創建的對象的強引用。
幸運的是,UIKit提供了一些方法去接收低內存警告:
實現App代理中的applicationDidReceiveMemoryWarning:方法。 重載你自定義UIViewController子類中的didReceiveMemoryWarning方法。 注冊接收UIApplicationDidReceiveMemoryWarningNotification的通知一旦收到這些警告,你的處理方法必須立刻響應並釋放不必要的內存。
舉例,如果視圖當前不可見,UIViewController的默認行為是清除這些視圖;子類可以通過清除額外的數據結構來補充父類的默認行為。一個應用程序維護一個圖片的緩存,沒有在屏幕上的圖片都會被釋放。
一旦收到內存警告,釋放可能的全部內存是很重要的,否則你就有讓你的app被系統殺死的的風險。
盡管如此,開始撲殺對象釋放內存的時候要小心,因為你需要保證它們會在之後重新創建。當你開發app的時候,用你的模擬器上的模擬內存警告功能測試這種情況。
13)重用大開銷對象
有的對象的初始化非常慢--NSDateFormatter 和NSCalendar是兩個例子,但是你不能避免使用它們,當你從JSON/XML響應中解析日期時。
避免使用這些對象時的性能瓶頸,試著盡可能的重用這些對象。你可以加入你的類中成為一個屬性,也可以創建為靜態變量。
注意如果你選擇了第二種方式,這個對象在app運行的時候會一直保持在內存裡,像單例一樣。
下面這段代碼演示了NSDateFomatter作為一個屬性的lazy加載,第一次被調用然後創建它,之後就使用已創建在的實例
查看源碼打印?01
// in your .h or inside a class extension
02
03
@property (nonatomic, strong) NSDateFormatter *formatter;
04
05
06
// inside the implementation (.m)
07
08
// When you need, just use self.formatter
09
10
- (NSDateFormatter *)formatter {
11
12
if(! _formatter) {
13
14
_formatter = [[NSDateFormatter alloc] init];
15
16
_formatter.dateFormat = @"EEE MMM dd HH:mm:ss Z yyyy";// twitter date format
17
18
}
19
20
return_formatter;
21
22
}
同樣要記住設置一個NSDateFormatter的日期格式幾乎跟創建一個新的一樣慢。因此,如果在你的應用中你頻繁需要處理多個日期格式,你的代碼應該獲利於初始化創建,重用,多個NSDateFormatter對象。
14)使用精靈表
你是一個游戲開發者嗎?精靈表是你的好朋友之一.精靈表讓繪制比標准屏幕繪制方法更快速,消耗更少的內存。
這裡有兩個很棒的精靈表使用的教程
How To Use Animations and Sprite Sheets in Cocos2DHow to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats第二個教程詳細覆蓋了像素格式,它可以對游戲性能有一個可衡量的影響。
如果對精靈表還不是很熟悉,一個很好的介紹SpriteSheets – The Movie, Part 1andPart 2. 這些視頻的作者是Andreas L?w,一個最流行的創建精靈表的工具Texture Packer的創建者。
除了使用精靈表之外,之前已經說到的內容也可以用在游戲上.舉個例子,如果你的游戲有很多精靈,比如在標准的敵人或炮彈射擊游戲,你可以重用精靈表額如是每次重新創建它們。
15)避免重復處理數據
很多app調用函數獲取遠程服務器上的數據.這些數據通常是通過JSON 或者 XML格式來傳輸。非常重要的是在請求和接收數據的時候努力在兩端使用相同的數據結構。
理由?在內存中操縱數據以合適你的數據結構是非常昂貴的。
比如,如果你需要在表格視圖中顯示數據,最好請求和接收數據是數組的格式,以避免任何中間操縱數據,使其適合你在app中使用的數據結構
相似的,如果你的應用程序依賴於訪問特定值的鍵,那麼你可能會想要請求和接收一個鍵/值對的字典
通過第一次就獲取正確格式的數據,在自己的應用程序中你就會避免很多的重復處理工作,使數據符合你的選擇的結構。
16)選擇正確的數據格式
你可以有很多方法從web 服務中傳遞數據到你的app中
JSON 是一種通常比XML小且解析更快的格式,它的傳輸的內容也比較小。自iOS5起,內置的JSON解析很好用built-in JSON deserialization
盡管如此,XML的一個優勢當你使用SAXparsing方法時,你可以傳輸過程中讀取它,在面的非常大的數據時,你不必像JSON一樣在數據下載完之後才開始讀取。
17)適當的設置背景圖片
像iOS編碼的其他工作一樣,至少有兩種不同方式去替換你視圖的背景圖片。
你可以設置你的視圖的背景顏色為UIColor的colorWithPatternImage創建的顏色。 你可以添加一個UIImageView子試圖給View如果你有全尺寸的背景圖片,你絕對要用UIImageView,因為UIColor的colorWithPatternImage是重復的創建小的模式圖片,在這種情況下用UIImageView方式會節約很多內存。
查看源碼打印?1
// You could also achieve the same result in Interface Builder
2
3
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
4
5
[self.view addSubview:backgroundView];
盡管如此,如果你計劃用模式圖片背景,你應該是用UIColor的colorWithPatternImage。它更快一些,而且這種情況不會使用很多內存。
查看源碼打印?1
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
18)減少你的網絡占用
UIWebView 是非常游泳的.它非常容易用來顯示web內容,甚至創建你app的視窗。這些都是標准UIKit 空間很難做到的。
盡管如此,你可能注意你可以用在你的app中的UIWebView組件並沒有Apple的Safari app快。這是Webkit’s的Nitro引擎的限制使用。JIT compilation.
所以為了獲得最佳的性能,你需要調整你的HTML。第一件事是盡可能多的避免Javascript,包括避免大的框架比如jQuery。有時使用vanilla Javascript取代依賴的框架會快很多。
隨時隨地遵循異步加載Javascript文件的實踐。特別當它們不直接影響到頁面表現的時候,比如分析腳本。
最後,總是要意識到你在用的圖片,保持圖片的正確尺寸。正如這個教程前面所提到的,利用精靈表的優勢來節約內存和提高速度。
想要獲取更多的信息,看看WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS.
19)設置陰影路徑
你需要給視圖或者layer添加一個陰影,你應該怎麼做?
大多數開發者是添加QuartzCore框架到工程中,然後寫如下代碼:
查看源碼打印?01
#import 02
03
// Somewhere later ...
04
05
UIView *view = [[UIView alloc] init];
06
07
// Setup the shadow ...
08
09
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
10
11
view.layer.shadowRadius = 5.0f;
12
13
view.layer.shadowOpacity = 0.6;
看起來非常簡單,是吧?
不好的是這個方法有一個問題。核心動畫必須要先做一幕動畫確定視圖具體形狀之後才渲染陰影,這是非常費事的操作。
這裡有個替代方法讓系統更好的渲染,設置陰影路徑:
查看源碼打印?1
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
如果你想知道這個內容的更多技巧,Mark Pospesel寫過一篇post aboutshadowPath.
設置陰影路徑,iOS不需要總是計算如何繪制陰影。而是用已經計算好的的路徑。壞消息是它依賴與你的視圖格式,你是視圖可能很難計算這個路徑。另一個問題是你需要在每次視圖的框架改變時更新陰影路徑。
20)優化你的表格視圖
表格視圖需要快速的滾動,如果不能,用戶能確切注意到很滯後。
為了讓你的表格視圖流暢的滾動,保證你實現了下列的建議。
通過正確的reuseIdentifier重用cells 盡量多的設置views 為不透明,包括cell本身。 避免漸變,圖像縮放,屏幕以外的繪制。 如果行高不總是一樣,緩存它們。 如果cell顯示的內容來自網絡,確保異步和緩存。 使用shadowPath來建立陰影。 減少子視圖的數目。 cellForRowAtIndexPath:中做盡量少的工作,如果需要做相同的工作,那麼只做一次並緩存結果。 使用適當的數據結構存儲你要的信息,不同的結構有對於不同的操作有不同的代價。 使用rowHeight,sectionFooterHeight,sectionHeaderHeight為常數,而不是詢問代理。
21)選擇正確的數據存儲方式
當要存儲和讀取大數據的時候你的選擇是什麼?
你有一些選項,包括:
使用NSUserDefaults存儲它們。 存儲在結構化文件中,XML,JSON,Plist格式中。 是用NSCoding打包? 存儲在本地數據庫,如SQLite 使用NSDataNSUserDefaults有什麼問題呢?雖然說NSUserDefaults是好而且簡單,它確實很好只有當你有很少的數據要存(像你的等級,或者音量是開還是關)。一旦你接觸大數據,會有更好的其他選擇。
保存在結構化文件中也可能有問題。一般的,在解析之前,你需要加載整個文件到內存中,這是非常耗時的操作。你可以使用SAX去處理XML文件,但是那是一個復雜的作法。同時你加載了全部的對象進內存,其中有你想要的也有不想要的。
那麼NSCoding怎麼樣呢?不幸的是,它也同樣要讀寫文件,跟上面說的方法有同樣的問題。
你最好的解決方法是使用SQLite或者 Core Data. 通過這些技術,你可以執行特定的查詢只加載需要的對象,避免強力搜索方法來檢索數據。性能方面,SQLite和Core Data 非常接近。
SQLite 和 Core Data最大的不同就是它們的使用方法。Core Data呈現為一個對象圖模型,但是SQLite是一個傳統的DBMS(數據庫管理系統).通常Apple建議你用Core Data,但是除非你有特殊的原因不讓你你會想避開它,使用更低級的SQLite。
如果在你的app中使用SQLite,一個方便的庫FMDB允許你使用SQLite而不用專研SQLite的C API。
高級性能技巧
尋找一些精英的方式去成為十足的代碼忍者?這些高級性能技巧可以合適的時候使用讓你的app運行得盡可能的高效。
22)加速啟動時間
App的啟動時間非常重要,特別是第一次啟動的時候。第一影響意味著太多了!
最大的事情是保證你的App開始盡量的快,盡量的多的執行異步任務,不如網絡請求,數據庫訪問,或者數據解析。
盡量避免臃腫的XIBs,因為你在主線程中加載。但是在故事板中不會有這個問題,所以盡量用它們。
Note: 監察人不會運行你的app在Xcode調試中, 所以確保測試啟動性能時斷開與Xcode的連接。
23)使用自動釋放池
NSAutoreleasePool負責釋放在代碼塊中的自動釋放對象。通常,它是被UIKit自動調用的。但是也有一些場景我們需要手動創建NSAutoreleasePools。
舉個例子,如果你創建太多的臨時對象在你的代碼中,你會注意到你的內存用量會增加直到對象被釋放掉。問題是內存只有在UIKit排空(drains)自動釋放池的時候才能被釋放,這意味著內存被占用的時間超過了需要。
好消息是你可以在你的@autoreleasepool段中創建臨時對象來避免上述情況。代碼如下所示。
查看源碼打印?01
NSArray *urls = <# An array of file URLs #>;
02
for (NSURL *url in urls) {
03
04
@autoreleasepool {
05
06
NSError *error;
07
08
NSString *fileContents = [NSString stringWithContentsOfURL:url
09
10
encoding:NSUTF8StringEncoding error:&error];
11
12
/* Process the string, creating and autoreleasing more objects. */
13
14
}
15
16
}
在每次迭代之後會自動釋放所有的對象。
你可以閱讀更多關於NSAutoreleasePool的內容Apple’s official documentation.
24)緩存圖像
這裡有兩種方法去加載app束中的Image,第一個常見的方式是用imageNamed. 第二個是使用imageWithContentsOfFile
為什麼會有兩種方法,它們有效率嗎?
imageNamed 在載入時有緩存的優勢。文檔documentation forimageNamed是這樣解釋的:
這個方法看起來在系統緩存一個圖像對象並指定名字,如果存在則返回對象,如果匹配圖像的對象不在緩存中,這個方法會從指定的文件中加載數據,並緩存它,然後返回結果對象。
作為替代,imageWithContendsOfFile 簡單的載入圖像並不會緩存。
這兩個方法的的演示片段如下:
查看源碼打印?1
UIImage *img = [UIImage imageNamed:@"myImage"];// caching
2
3
// or
4
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];// no caching
如果你加載只使用一次大圖片,那就不需要緩存。這種情況imageWithContendsOfFile會非常好,這種方式不會浪費內存來緩存圖片。什麼時候使用哪一種呢?
然而,imageNamed 對於要重用的圖片來說是更好的選擇,這種方法節約了經常的從磁盤加載圖片的時間。
25)盡可能避免日期格式化器
如果你要用NSDateFormatter來解析日期數據,你就得小心對待了。之前提到過,盡量的重用NSDateFormatters總是一個好的想法。
然而,如果你需要更快的速度,你可以使用C代替NSDateFormatter來解析日期。Sam Soffes寫了一篇blog post about this topic來說明如何用代碼來解析ISO-8601日期串。盡管如此,你可以很容易的修改他的代碼例子來適應你的特殊需求。
噢,聽起來很棒,但是你相信有更好的辦法嗎?
如果你能控制你所處理日期的格式,盡可能的選擇使用Unix timestamps。Unix時間戳是簡單的整數代表從某個起始時間點開始到現在的秒數。這個起始點通常是1970年1月1日 UTC 00:00:00。
你可以容易的把時間戳轉換為NSDate,如下面所示:
查看源碼打印?1
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
2
3
return[NSDate dateWithTimeIntervalSince1970:timestamp];
4
5
}
這甚至比C函數更快
注意,很多WEB APIs返回時間戳是毫秒,因為這對於javascript最終來使用和處理數據是非常常見的。只要記住將這個時間戳除以1000再傳遞給dateFromUnixTimestamp方法即可。