內(nèi)存優(yōu)化的建議和技巧

轉(zhuǎn)載自:http://www.cnblogs.com/CoderAlex/p/5264070.html
性能對 iOS 應(yīng)用的開發(fā)尤其重要,如果你的應(yīng)用失去反應(yīng)或者很慢忧换,失望的用戶會把他們的失望寫滿App Store的評論。然而由于iOS設(shè)備的限制,有時搞好性能是一件難事消返。開發(fā)過程中你會有很多需要注意的事項,你也很容易在做出選擇時忘記考慮性能影響耘拇。

這正是我寫下這篇文章的原因撵颊。這篇文章以一個方便查看的核對表的形式整合了你可以用來提升你app性能的25條建議和技巧。

請耐心讀完這篇文章惫叛,為你未來的app提個速倡勇!

注意:每在優(yōu)化代碼之前,你都要注意一個問題嘉涌,不要養(yǎng)成”預(yù)優(yōu)化”代碼的錯誤習(xí)慣译隘。時常使用Instruments去profile你的代碼來發(fā)現(xiàn)需要提升的方面。Matt Galloway寫過一篇很棒的如何利用Instruments來優(yōu)化代碼的文章洛心。

還要注意的是固耘,這里列出的其中一些建議是有代價的,所建議的方式會提升app的速度或者使它更加高效词身,但也可能需要花很多功夫去應(yīng)用或者使代碼變得更加復(fù)雜厅目,所以要仔細(xì)選擇。

目錄
我要給出的建議將分為三個不同的等級: 入門級法严、 中級和進階級:

入門級(這是些你一定會經(jīng)常用在你app開發(fā)中的建議)

  1. 用ARC管理內(nèi)存

  2. 在正確的地方使用reuseIdentifier

  3. 盡可能使Views不透明

  4. 避免龐大的XIB

  5. 不要block主線程

  6. 在Image Views中調(diào)整圖片大小

  7. 選擇正確的Collection

  8. 打開gzip壓縮
    中級(這些是你可能在一些相對復(fù)雜情況下可能用到的)

  9. 重用和延遲加載Views

  10. Cache, Cache, 還是Cache损敷!

  11. 權(quán)衡渲染方法

  12. 處理內(nèi)存警告

  13. 重用大開銷的對象

  14. 使用Sprite Sheets

  15. 避免反復(fù)處理數(shù)據(jù)

  16. 選擇正確的數(shù)據(jù)格式

  17. 正確地設(shè)定Background Images

  18. 減少使用Web特性

  19. 設(shè)定Shadow Path

  20. 優(yōu)化你的Table View

  21. 選擇正確的數(shù)據(jù)存儲選項
    進階級(這些建議只應(yīng)該在你確信他們可以解決問題和得心應(yīng)手的情況下采用)

  22. 加速啟動時間

  23. 使用Autorelease Pool

  24. 選擇是否緩存圖片

  25. 盡量避免日期格式轉(zhuǎn)換
    無需贅述,讓我們進入正題吧~

初學(xué)者性能提升
這個部分致力于一些能提高性能的基本改變深啤。但所有層次的開發(fā)者都有可能會從這個記錄了一些被忽視的項目的小小的性能備忘錄里獲得一些提升拗馒。

  1. 用ARC管理內(nèi)存

ARC(Automatic Reference Counting, 自動引用計數(shù))和iOS5一起發(fā)布,它避免了最常見的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露溯街。它自動為你管理retain和release的過程诱桂,所以你就不必去手動干預(yù)了洋丐。

下面是你會經(jīng)常用來去創(chuàng)建一個View的代碼段:

1
2
3
4
UIView *view = [[UIView alloc] init];
// ...
[self.view addSubview:view];
[view release];
忘掉代碼段結(jié)尾的release簡直像記得吃飯一樣簡單。而ARC會自動在底層為你做這些工作挥等。

除了幫你避免內(nèi)存泄露友绝,ARC還可以幫你提高性能,它能保證釋放掉不再需要的對象的內(nèi)存肝劲。這都啥年代了迁客,你應(yīng)該在你的所有項目里使用ARC!

這里有一些更多關(guān)于ARC的學(xué)習(xí)資源:

Apple’s official documentation
Matthijs Hollemans’s Beginning ARC in iOS Tutorial
Tony Dahbura’s How To Enable ARC in a Cocos2D 2.X Project
If you still aren’t convinced of the benefits of ARC, check out this article on eight myths about ARC to really convince you why you should be using it!
ARC當(dāng)然不能為你排除所有內(nèi)存泄露的可能性。由于阻塞, retain 周期, 管理不完善的CoreFoundation object(還有C結(jié)構(gòu))或者就是代碼太爛依然能導(dǎo)致內(nèi)存泄露辞槐。

這里有一篇很棒的介紹ARC不能做到以及我們該怎么做的文章 http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html掷漱。

  1. 在正確的地方使用 reuseIdentifier

一個開發(fā)中常見的錯誤就是沒有給UITableViewCells, UICollectionViewCells榄檬,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier卜范。

為了性能最優(yōu)化,table view用 tableView:cellForRowAtIndexPath: 為rows分配cells的時候丙号,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell。 一個table view維持一個隊列的數(shù)據(jù)可重用的UITableViewCell對象缰冤。

不使用reuseIdentifier的話犬缨,每顯示一行table view就不得不設(shè)置全新的cell。這對性能的影響可是相當(dāng)大的棉浸,尤其會使app的滾動體驗大打折扣怀薛。

自iOS6起,除了UICollectionView的cells和補充views迷郑,你也應(yīng)該在header和footer views中使用reuseIdentifiers枝恋。

想要使用reuseIdentifiers的話,在一個table view中添加一個新的cell時在data source object中添加這個方法:

1
2
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
這個方法把那些已經(jīng)存在的cell從隊列中排除嗡害,或者在必要時使用先前注冊的nib或者class創(chuàng)造新的cell焚碌。如果沒有可重用的cell,你也沒有注冊一個class或者nib的話霸妹,這個方法返回nil十电。

3.盡量把views設(shè)置為不透明

如果你有不透明的Views,你應(yīng)該設(shè)置它們的opaque屬性為YES叹螟。

原因是這會使系統(tǒng)用一個最優(yōu)的方式渲染這些views鹃骂。這個簡單的屬性在IB或者代碼里都可以設(shè)定。

Apple的文檔對于為圖片設(shè)置不透明屬性的描述是:

(opaque)這個屬性給渲染系統(tǒng)提供了一個如何處理這個view的提示罢绽。如果設(shè)為YES畏线, 渲染系統(tǒng)就認(rèn)為這個view是完全不透明的,這使得渲染系統(tǒng)優(yōu)化一些渲染過程和提高性能良价。如果設(shè)置為NO寝殴,渲染系統(tǒng)正常地和其它內(nèi)容組成這個View蒿叠。默認(rèn)值是YES。

在相對比較靜止的畫面中杯矩,設(shè)置這個屬性不會有太大影響栈虚。然而當(dāng)這個view嵌在scroll view里邊,或者是一個復(fù)雜動畫的一部分史隆,不設(shè)置這個屬性的話會在很大程度上影響app的性能魂务。

你可以在模擬器中用Debug\Color Blended Layers選項來發(fā)現(xiàn)哪些view沒有被設(shè)置為opaque。目標(biāo)就是泌射,能設(shè)為opaque的就全設(shè)為opaque!

  1. 避免過于龐大的XIB

iOS5中加入的Storyboards(分鏡)正在快速取代XIB粘姜。然而XIB在一些場景中仍然很有用。比如你的app需要適應(yīng)iOS5之前的設(shè)備熔酷,或者你有一個自定義的可重用的view,你就不可避免地要用到他們孤紧。

如果你不得不XIB的話,使他們盡量簡單拒秘。嘗試為每個Controller配置一個單獨的XIB号显,盡可能把一個View Controller的view層次結(jié)構(gòu)分散到單獨的XIB中去。

需要注意的是躺酒,當(dāng)你加載一個XIB的時候所有內(nèi)容都被放在了內(nèi)存里押蚤,包括任何圖片。如果有一個不會即刻用到的view羹应,你這就是在浪費寶貴的內(nèi)存資源了揽碘。Storyboards就是另一碼事兒了,storyboard僅在需要時實例化一個view controller.

當(dāng)家在XIB是园匹,所有圖片都被chache雳刺,如果你在做OS X開發(fā)的話,聲音文件也是裸违。Apple在相關(guān)文檔中的記述是:

當(dāng)你加載一個引用了圖片或者聲音資源的nib時掖桦,nib加載代碼會把圖片和聲音文件寫進內(nèi)存。在OS X中供汛,圖片和聲音資源被緩存在named cache中以便將來用到時獲取滞详。在iOS中,僅圖片資源會被存進named caches紊馏。取決于你所在的平臺料饥,使用NSImage 或UIImage 的imageNamed:方法來獲取圖片資源。

很明顯朱监,同樣的事情也發(fā)生在storyboards中岸啡,但我并沒有找到任何支持這個結(jié)論的文檔。如果你了解這個操作赫编,寫信給我巡蘸!

想要了解更多關(guān)于storyboards的內(nèi)容的話你可以看看 Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1 和 Part 2

  1. 不要阻塞主線程

永遠不要使主線程承擔(dān)過多奋隶。因為UIKit在主線程上做所有工作,渲染悦荒,管理觸摸反應(yīng)唯欣,回應(yīng)輸入等都需要在它上面完成。

一直使用主線程的風(fēng)險就是如果你的代碼真的block了主線程搬味,你的app會失去反應(yīng)境氢。這。碰纬。萍聊。正是在App Store中拿到一顆星的捷徑 :]

大部分阻礙主進程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作,比如存儲或者網(wǎng)絡(luò)悦析。

你可以使用NSURLConnection異步地做網(wǎng)絡(luò)操作:

  • (void)sendAsynchronousRequest:(NSURLRequest )request queue:(NSOperationQueue )queue completionHandler:(void (^)(NSURLResponse, NSData, NSError*))handler

或者使用像 AFNetworking這樣的框架來異步地做這些操作寿桨。

如果你需要做其它類型的需要耗費巨大資源的操作(比如時間敏感的計算或者存儲讀寫)那就用 Grand Central Dispatch,或者 NSOperation 和 NSOperationQueues.

下面代碼是使用GCD的模板

1
2
3
4
5
6
7
8
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// switch to a background thread and perform your expensive operation

dispatch_async(dispatch_get_main_queue(), ^{
    // switch back to the main thread to update your UI

});

});
發(fā)現(xiàn)代碼中有一個嵌套的dispatch_async嗎强戴?這是因為任何UIKit相關(guān)的代碼需要在主線程上進行亭螟。

如果你對 NSOperation 或者GCD 的細(xì)節(jié)感興趣的話,看看Ray Wenderlich的 Multithreading and Grand Central Dispatch on iOS for Beginners骑歹, 還有 Soheil Azarpour 的 How To Use NSOperations and NSOperationQueues 教程预烙。

  1. 在Image Views中調(diào)整圖片大小

如果要在UIImageView中顯示一個來自bundle的圖片,你應(yīng)保證圖片的大小和UIImageView的大小相同陵刹。在運行中縮放圖片是很耗費資源的默伍,特別是UIImageView嵌套在UIScrollView中的情況下欢嘿。

如果圖片是從遠端服務(wù)加載的你不能控制圖片大小衰琐,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后炼蹦,最好是用background thread羡宙,縮放一次,然后在UIImageView中使用縮放后的圖片掐隐。

  1. 選擇正確的Collection

學(xué)會選擇對業(yè)務(wù)場景最合適的類或者對象是寫出能效高的代碼的基礎(chǔ)狗热。當(dāng)處理collections時這句話尤其正確。

Apple有一個 Collections Programming Topics 的文檔詳盡介紹了可用的classes間的差別和你該在哪些場景中使用它們虑省。這對于任何使用collections的人來說是一個必讀的文檔匿刮。

呵呵,我就知道你因為太長沒看…這是一些常見collection的總結(jié):

Arrays: 有序的一組值探颈。使用index來lookup很快熟丸,使用value lookup很慢, 插入/刪除很慢伪节。
Dictionaries: 存儲鍵值對光羞。 用鍵來查找比較快绩鸣。
Sets: 無序的一組值。用值來查找很快纱兑,插入/刪除很快呀闻。

  1. 打開gzip壓縮

大量app依賴于遠端資源和第三方API,你可能會開發(fā)一個需要從遠端下載XML, JSON, HTML或者其它格式的app潜慎。

問題是我們的目標(biāo)是移動設(shè)備捡多,因此你就不能指望網(wǎng)絡(luò)狀況有多好。一個用戶現(xiàn)在還在edge網(wǎng)絡(luò)勘纯,下一分鐘可能就切換到了3G局服。不論什么場景,你肯定不想讓你的用戶等太長時間驳遵。

減小文檔的一個方式就是在服務(wù)端和你的app中打開gzip淫奔。這對于文字這種能有更高壓縮率的數(shù)據(jù)來說會有更顯著的效用。

好消息是堤结,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮唆迁,當(dāng)然AFNetworking這些基于它的框架亦然。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出竞穷。

如果你不知道如何利用Apache或者IIS(服務(wù)器)來打開gzip唐责,可以讀下這篇文章。

中級性能提升
你確信你已經(jīng)掌握了前述那些基礎(chǔ)級的優(yōu)化方案了嗎瘾带?但實際情況是鼠哥,有時一些解決方案并不像那些一樣明顯,它們往往嚴(yán)重依賴于你如何架構(gòu)和書寫你的app看政。下面的這些建議就是針對這些場景的朴恳。

  1. 重用和延遲加載(lazy load) Views

更多的view意味著更多的渲染,也就是更多的CPU和內(nèi)存消耗允蚣,對于那種嵌套了很多view在UIScrollView里邊的app更是如此于颖。

這里我們用到的技巧就是模仿UITableViewUICollectionView的操作: 不要一次創(chuàng)建所有的subview,而是當(dāng)需要時才創(chuàng)建嚷兔,當(dāng)它們完成了使命森渐,把他們放進一個可重用的隊列中。

這樣的話你就只需要在滾動發(fā)生時創(chuàng)建你的views冒晰,避免了不劃算的內(nèi)存分配同衣。

創(chuàng)建views的能效問題也適用于你app的其它方面。想象一下一個用戶點擊一個按鈕的時候需要呈現(xiàn)一個view的場景壶运。有兩種實現(xiàn)方法:

  1. 創(chuàng)建并隱藏這個view當(dāng)這個screen加載的時候耐齐,當(dāng)需要時顯示它;
  2. 當(dāng)需要時才創(chuàng)建并展示。
    每個方案都有其優(yōu)缺點蚪缀。

用第一種方案的話因為你需要一開始就創(chuàng)建一個view并保持它直到不再使用秫逝,這就會更加消耗內(nèi)存。然而這也會使你的app操作更敏感因為當(dāng)用戶點擊按鈕的時候它只需要改變一下這個view的可見性询枚。

第二種方案則相反-消耗更少內(nèi)存违帆,但是會在點擊按鈕的時候比第一種稍顯卡頓。

  1. Cache, Cache, 還是Cache!

一個極好的原則就是金蜀,緩存所需要的刷后,也就是那些不大可能改變但是需要經(jīng)常讀取的東西。

我們能緩存些什么呢渊抄?一些選項是尝胆,遠端服務(wù)器的響應(yīng),圖片护桦,甚至計算結(jié)果含衔,比如UITableView的行高。

NSURLConnection默認(rèn)會緩存資源在內(nèi)存或者存儲中根據(jù)它所加載的HTTP Headers二庵。你甚至可以手動創(chuàng)建一個NSURLRequest然后使它只加載緩存的值贪染。

下面是一個可用的代碼段,你可以可以用它去為一個基本不會改變的圖片創(chuàng)建一個NSURLRequest并緩存它:

1
2
3
4
5
6
7
8
9
10

  • (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代碼了。

如果想了解更多關(guān)于HTTP caching, NSURLCache, NSURLConnection的相關(guān)知識因妙,可以讀下這篇文章()

如果你需要緩存其它不是HTTP Request的東西痰憎,你可以用NSCache。

NSCache和NSDictionary類似攀涵,不同的是系統(tǒng)回收內(nèi)存的時候它會自動刪掉它的內(nèi)容铣耘。 Mattt Thompson有一篇很棒的關(guān)于它的文章::http://nshipster.com/nscache/

如果你對HTTP感興趣可以讀下Google的這篇 best-practices document on HTTP caching。

  1. 權(quán)衡渲染方法

在iOS中可以有很多方法做出漂亮的按鈕汁果。你可以用整幅的圖片涡拘,可調(diào)大小的圖片玲躯,uozhe可以用CALayer据德, CoreGraphics甚至OpenGL來畫它們。

當(dāng)然每個不同的解決方法都有不同的復(fù)雜程度和相應(yīng)的性能跷车。有一篇Apple UIKit team中的一員Andy Matuschak推薦過的很棒的關(guān)于graphic性能的帖子很值得一讀棘利。

簡單來說,就是用事先渲染好的圖片更快一些朽缴,因為如此一來iOS就免去了創(chuàng)建一個圖片再畫東西上去然后顯示在屏幕上的程序善玫。問題是你需要把所有你需要用到的圖片放到app的bundle里面,這樣就增加了體積 – 這就是使用可變大小的圖片更好的地方了: 你可以省去一些不必要的空間密强,也不需要再為不同的元素(比如按鈕)來做不同的圖茅郎。

然而蜗元,使用圖片也意味著你失去了使用代碼調(diào)整圖片的機動性,你需要一遍又一遍不斷地重做他們系冗,這樣就很浪費時間了,而且你如果要做一個動畫效果,雖然每幅圖只是一些細(xì)節(jié)的變化你就需要很多的圖片造成bundle大小的不斷增大走敌。

總得來說蠢熄,你需要權(quán)衡一下利弊,到底是要性能能還是要bundle保持合適的大小奔害。

  1. 處理內(nèi)存警告

一旦系統(tǒng)內(nèi)存過低楷兽,iOS會通知所有運行中app。在官方文檔中是這樣記述:

如果你的app收到了內(nèi)存警告华临,它就需要盡可能釋放更多的內(nèi)存芯杀。最佳方式是移除對緩存,圖片object和其他一些可以重創(chuàng)建的objects的strong references.

幸運的是雅潭,UIKit提供了幾種收集低內(nèi)存警告的方法:

在app delegate中使用applicationDidReceiveMemoryWarning: 的方法
在你的自定義UIViewController的子類(subclass)中覆蓋didReceiveMemoryWarning
注冊并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
一旦收到這類通知瘪匿,你就需要釋放任何不必要的內(nèi)存使用。

例如寻馏,UIViewController的默認(rèn)行為是移除一些不可見的view棋弥, 它的一些子類則可以補充這個方法,刪掉一些額外的數(shù)據(jù)結(jié)構(gòu)诚欠。一個有圖片緩存的app可以移除不在屏幕上顯示的圖片顽染。

這樣對內(nèi)存警報的處理是很必要的,若不重視轰绵,你的app就可能被系統(tǒng)殺掉粉寞。

然而,當(dāng)你一定要確認(rèn)你所選擇的object是可以被重現(xiàn)創(chuàng)建的來釋放內(nèi)存左腔。一定要在開發(fā)中用模擬器中的內(nèi)存提醒模擬去測試一下唧垦。

  1. 重用大開銷對象

Reuse Expensive Objects

一些objects的初始化很慢,比如NSDateFormatter和NSCalendar液样。然而振亮,你又不可避免地需要使用它們,比如從JSON或者XML中解析數(shù)據(jù)鞭莽。

想要避免使用這個對象的瓶頸你就需要重用他們坊秸,可以通過添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來實現(xiàn)。

注意如果你要選擇第二種方法澎怒,對象會在你的app運行時一直存在于內(nèi)存中褒搔,和單例(singleton)很相似。

下面的代碼說明了使用一個屬性來延遲加載一個date formatter. 第一次調(diào)用時它會創(chuàng)建一個新的實例,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實例:

1
2
3
4
5
6
7
8
9
10
11
12
// 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;
    }
    還需要注意的是星瘾,其實設(shè)置一個NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的走孽!所以如果你的app需要經(jīng)常進行日期格式處理的話,你會從這個方法中得到不小的性能提升琳状。
  1. 使用Sprite Sheets

你是一個游戲開發(fā)者嗎融求,那么Sprite sheets一定是一個你的最好的朋友了。Sprite sheet可以讓渲染速度加快算撮,甚至比標(biāo)準(zhǔn)的屏幕渲染方法節(jié)省內(nèi)存生宛。

我們有兩個很好的關(guān)于Sprite的教程:

How To Use Animations and Sprite Sheets in Cocos2D
How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats
第二個教程涵蓋了可能在很大程度上影響你游戲性能的pixel格式的細(xì)節(jié)。

如果你對于spirte sheet還不是很熟悉肮柜,可以看下這兩個(youtube)視頻SpriteSheets – The Movie, Part 1 和 Part 2陷舅。視頻的作者是創(chuàng)建Sprite sheet很流行的工具之一Texture Packer的作者Andreas L?w。

除了使用Sprite sheets审洞,其它寫在這里的建議當(dāng)然也可以用于游戲開發(fā)中莱睁。比如你需要很多的Sprite sheets,像敵人芒澜,導(dǎo)彈之類的動作類必備元素仰剿,你可以重用這些sprites而不用每次都要重新創(chuàng)建。

  1. 避免反復(fù)處理數(shù)據(jù)

許多應(yīng)用需要從服務(wù)器加載功能所需的常為JSON或者XML格式的數(shù)據(jù)痴晦。在服務(wù)器端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)很重要南吮。在內(nèi)存中操作數(shù)據(jù)使它們滿足你的數(shù)據(jù)結(jié)構(gòu)是開銷很大的。

比如你需要數(shù)據(jù)來展示一個table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變誊酌。

類似的部凑,如果需要從特定key中取數(shù)據(jù),那么就使用鍵值對的dictionary碧浊。

  1. 選擇正確的數(shù)據(jù)格式

從app和網(wǎng)絡(luò)服務(wù)間傳輸數(shù)據(jù)有很多方案涂邀,最常見的就是JSON和XML。你需要選擇對你的app來說最合適的一個箱锐。

解析JSON會比XML更快一些比勉,JSON也通常更小更便于傳輸。從iOS5起有了官方內(nèi)建的JSON deserialization 就更加方便使用了驹止。

但是XML也有XML的好處浩聋,比如使用SAX 來解析XML就像解析本地文件一樣,你不需像解析json一樣等到整個文檔下載完成才開始解析幢哨。當(dāng)你處理很大的數(shù)據(jù)的時候就會極大地減低內(nèi)存消耗和增加性能赡勘。

  1. 正確設(shè)定背景圖片

在View里放背景圖片就像很多其它iOS編程一樣有很多方法:

使用UIColor的 colorWithPatternImage來設(shè)置背景色嫂便;
在view中添加一個UIImageView作為一個子View捞镰。
如果你使用全畫幅的背景圖,你就必須使用UIImageView因為UIColor的colorWithPatternImage是用來創(chuàng)建小的重復(fù)的圖片作為背景的。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存:

1
2
3
// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];
如果你用小圖平鋪來創(chuàng)建背景岸售,你就需要用UIColor的colorWithPatternImage來做了践樱,它會更快地渲染也不會花費很多內(nèi)存:

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

  1. 減少使用Web特性

UIWebView很有用,用它來展示網(wǎng)頁內(nèi)容或者創(chuàng)建UIKit很難做到的動畫效果是很簡單的一件事凸丸。

但是你可能有注意到UIWebView并不像驅(qū)動Safari的那么快拷邢。這是由于以JIT compilation 為特色的Webkit的Nitro Engine的限制。

所以想要更高的性能你就要調(diào)整下你的HTML了屎慢。第一件要做的事就是盡可能移除不必要的javascript瞭稼,避免使用過大的框架。能只用原生js就更好了腻惠。

另外环肘,盡可能異步加載例如用戶行為統(tǒng)計script這種不影響頁面表達的javascript。

最后集灌,永遠要注意你使用的圖片悔雹,保證圖片的符合你使用的大小。使用Sprite sheet提高加載速度和節(jié)約內(nèi)存欣喧。

更多相關(guān)信息可以看下 WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS

  1. 設(shè)定Shadow Path

如何在一個View或者一個layer上加一個shadow呢腌零,QuartzCore框架是很多開發(fā)者的選擇:

1
2
3
4
5
6
7
8
9

import <QuartzCore/QuartzCore.h>

// Somewhere later ...
UIView *view = [[UIView alloc] init];

// Setup the shadow ...
view.layer.shadowOffset = CGSizeMake(-1.0f, 1.0f);
view.layer.shadowRadius = 5.0f;
view.layer.shadowOpacity = 0.6;
看起來很簡單,對吧唆阿。

可是益涧,壞消息是使用這個方法也有它的問題… Core Animation不得不先在后臺得出你的圖形并加好陰影然后才渲染,這開銷是很大的驯鳖。

使用shadowPath的話就避免了這個問題:
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];

使用shadow path的話iOS就不必每次都計算如何渲染饰躲,它使用一個預(yù)先計算好的路徑。但問題是自己計算path的話可能在某些View中比較困難臼隔,且每當(dāng)view的frame變化的時候你都需要去update shadow path.

想了解更多可以看看Mark Pospesel的這篇嘹裂。

  1. 優(yōu)化Table View

Table view需要有很好的滾動性能,不然用戶會在滾動過程中發(fā)現(xiàn)動畫的瑕疵摔握。

為了保證table view平滑滾動寄狼,確保你采取了以下的措施:

正確使用reuseIdentifier來重用cells
盡量使所有的view opaque,包括cell自身
避免漸變氨淌,圖片縮放泊愧,后臺選人
緩存行高
如果cell內(nèi)現(xiàn)實的內(nèi)容來自web,使用異步加載盛正,緩存請求結(jié)果
使用shadowPath來畫陰影
減少subviews的數(shù)量
盡量不適用cellForRowAtIndexPath:删咱,如果你需要用到它,只用一次然后緩存結(jié)果
使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲數(shù)據(jù)
使用rowHeight, sectionFooterHeightsectionHeaderHeight來設(shè)定固定的高豪筝,不要請求delegate

  1. 選擇正確的數(shù)據(jù)存儲選項

當(dāng)存儲大塊數(shù)據(jù)時你會怎么做痰滋?

你有很多選擇摘能,比如:

使用NSUerDefaults
使用XML, JSON, 或者 plist
使用NSCoding存檔
使用類似SQLite的本地SQL數(shù)據(jù)庫
使用 Core Data
NSUserDefaults的問題是什么?雖然它很nice也很便捷敲街,但是它只適用于小數(shù)據(jù)团搞,比如一些簡單的布爾型的設(shè)置選項,再大點你就要考慮其它方式了

XML這種結(jié)構(gòu)化檔案呢多艇?總體來說逻恐,你需要讀取整個文件到內(nèi)存里去解析,這樣是很不經(jīng)濟的峻黍。使用SAX又是一個很麻煩的事情复隆。

NSCoding?不幸的是姆涩,它也需要讀寫文件昏名,所以也有以上問題。

在這種應(yīng)用場景下阵面,使用SQLite 或者 Core Data比較好轻局。使用這些技術(shù)你用特定的查詢語句就能只加載你需要的對象。

在性能層面來講样刷,SQLite和Core Data是很相似的仑扑。他們的不同在于具體使用方法。Core Data代表一個對象的graph model置鼻,但SQLite就是一個DBMS镇饮。Apple在一般情況下建議使用Core Data,但是如果你有理由不使用它箕母,那么就去使用更加底層的SQLite吧储藐。

如果你使用SQLite,你可以用FMDB(https://github.com/ccgus/fmdb)這個庫來簡化SQLite的操作嘶是,這樣你就不用花很多經(jīng)歷了解SQLite的C API了钙勃。

進階性能提示
想要一些是你成為程序猿忍者的精英級的建議嗎?下面這些提示可以幫你把你的app優(yōu)化到極致聂喇!

  1. 加速啟動時間

快速打開app是很重要的辖源,特別是用戶第一次打開它時,對app來講希太,第一印象太太太重要了克饶。

你能做的就是使它盡可能做更多的異步任務(wù),比如加載遠端或者數(shù)據(jù)庫數(shù)據(jù)誊辉,解析數(shù)據(jù)矾湃。

還是那句話,避免過于龐大的XIB堕澄,因為他們是在主線程上加載的邀跃。所以盡量使用沒有這個問題的Storyboards吧霉咨!

注意,用Xcode debug時watchdog并不運行坞嘀,一定要把設(shè)備從Xcode斷開來測試啟動速度

  1. 使用Autorelease Pool

NSAutoreleasePool負(fù)責(zé)釋放block中的autoreleased objects躯护。一般情況下它會自動被UIKit調(diào)用惊来。但是有些狀況下你也需要手動去創(chuàng)建它丽涩。

假如你創(chuàng)建很多臨時對象,你會發(fā)現(xiàn)內(nèi)存一直在減少直到這些對象被release的時候裁蚁。這是因為只有當(dāng)UIKit用光了autorelease pool的時候memory才會被釋放矢渊。

好消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時的對象來避免這個行為:

1
2
3
4
5
6
7
8
9
NSArray *urls = <# An array of file URLs #>;
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對象

更多關(guān)于NSAutoreleasePool請參考官方文檔。

  1. 選擇是否緩存圖片

常見的從bundle中加載圖片的方式有兩種枉证,一個是用imageNamed矮男,二是用imageWithContentsOfFile,第一種比較常見一點室谚。

既然有兩種類似的方法來實現(xiàn)相同的目的毡鉴,那么他們之間的差別是什么呢?

imageNamed的優(yōu)點是當(dāng)加載時會緩存圖片秒赤。imageNamed的文檔中這么說:
這個方法用一個指定的名字在系統(tǒng)緩存中查找并返回一個圖片對象如果它存在的話猪瞬。如果緩存中沒有找到相應(yīng)的圖片,這個方法從指定的文檔中加載然后緩存并返回這個對象入篮。

相反的陈瘦,imageWithContentsOfFile僅加載圖片。

下面的代碼說明了這兩種方法的用法:

1
2
3
UIImage *img = [UIImage imageNamed:@"myImage"]; // caching
// or
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"]; // no caching
那么我們應(yīng)該如何選擇呢潮售?

如果你要加載一個大圖片而且是一次性使用痊项,那么就沒必要緩存這個圖片,用imageWithContentsOfFile足矣酥诽,這樣不會浪費內(nèi)存來緩存它鞍泉。

然而,在圖片反復(fù)重用的情況下imageNamed是一個好得多的選擇肮帐。

  1. 避免日期格式轉(zhuǎn)換

如果你要用NSDateFormatter來處理很多日期格式塞弊,應(yīng)該小心以待。就像先前提到的泪姨,任何時候重用NSDateFormatters都是一個好的實踐游沿。

然而,如果你需要更多速度肮砾,那么直接用C是一個好的方案诀黍。Sam Soffes有一個不錯的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用來解析ISO-8601日期字符串的代碼,簡單重寫一下就可以拿來用了仗处。

嗯眯勾,直接用C來搞枣宫,看起來不錯了,但是你相信嗎吃环,我們還有更好的方案也颤!

如果你可以控制你所處理的日期格式,盡量選擇Unix時間戳郁轻。你可以方便地從時間戳轉(zhuǎn)換到NSDate:

1
2
3

  • (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
    return [NSDate dateWithTimeIntervalSince1970:timestamp];
    }
    這樣會比用C來解析日期字符串還快翅娶!

需要注意的是,許多web API會以微秒的形式返回時間戳好唯,因為這種格式在javascript中更方便使用竭沫。記住用dateFromUnixTimestamp之前除以1000就好了。

更多閱讀
下列這些WWDC視頻強烈推薦給想要提高app性能的開發(fā)者骑篙。你首先需要保證你有使你的Apple ID注冊為一個開發(fā)者身份才能看在這里看WWDC 2012的視頻蜕提。

406: Adopting Automatic Reference Counting

238: iOS App Performance: Graphics and Animations

242: iOS App Performance: Memory

235: iOS App Performance: Responsiveness

409: Learning Instruments

706: Networking Best Practices

514: OpenGL ES Tools and Techniques

506: Optimizing 2D Graphics and Animation Performance

601: Optimizing Web Content in UIWebViews and Websites on iOS

225: Up and Running: Making a Great Impression with Every Launch

一些01年的WWDC視頻也很有價值:

308: Blocks and Grand Central Dispatch in Practice

323: Introducing Automatic Reference Counting

312: iOS Performance and Power Optimization with Instruments

105: Polishing Your App: Tips and tricks to improve the responsiveness and performance

121: Understanding UIKit Rendering

其它一些值得看的視頻,大部分來自iOS 5 Tech Talks:

Your iOS App Performance Hitlist
Optimizing App Performance with Instruments
Understanding iOS View Compositing
基于《Your iOS App Performance Hitlist》這個Michael Jurewitz的視頻靶端,Ole Begemann寫了一篇文字總結(jié)的文章谎势。

Apple提供了一個非常有用的叫做“Performance Tuning | 性能調(diào)優(yōu)”的資源。

–EOF–

Write the code ,change the world!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末杨名,一起剝皮案震驚了整個濱河市脏榆,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌镣煮,老刑警劉巖姐霍,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異典唇,居然都是意外死亡镊折,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門介衔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恨胚,“玉大人,你說我怎么就攤上這事炎咖≡吲荩” “怎么了?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵乘盼,是天一觀的道長升熊。 經(jīng)常有香客問我,道長绸栅,這世上最難降的妖魔是什么级野? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮粹胯,結(jié)果婚禮上蓖柔,老公的妹妹穿的比我還像新娘辰企。我一直安慰自己,他們只是感情好况鸣,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布牢贸。 她就那樣靜靜地躺著,像睡著了一般镐捧。 火紅的嫁衣襯著肌膚如雪潜索。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天愤估,我揣著相機與錄音帮辟,去河邊找鬼速址。 笑死玩焰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的芍锚。 我是一名探鬼主播昔园,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼并炮!你這毒婦竟也來了默刚?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤逃魄,失蹤者是張志新(化名)和其女友劉穎荤西,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伍俘,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡邪锌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了癌瘾。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片觅丰。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖妨退,靈堂內(nèi)的尸體忽然破棺而出妇萄,到底是詐尸還是另有隱情,我是刑警寧澤咬荷,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布冠句,位于F島的核電站,受9級特大地震影響幸乒,放射性物質(zhì)發(fā)生泄漏懦底。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一逝变、第九天 我趴在偏房一處隱蔽的房頂上張望基茵。 院中可真熱鬧奋构,春花似錦、人聲如沸拱层。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽根灯。三九已至径缅,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間烙肺,已是汗流浹背纳猪。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留桃笙,地道東北人氏堤。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像搏明,于是被迫代替她去往敵國和親鼠锈。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

推薦閱讀更多精彩內(nèi)容