性能對(duì)iOS應(yīng)用的開發(fā)尤其重要,性能不佳會(huì)體現(xiàn)在:你的應(yīng)用失去反應(yīng)或者反應(yīng)很慢章姓。
分為三個(gè)不同的等級(jí): 入門級(jí)、 中級(jí)和進(jìn)階級(jí):
- 入門級(jí)(這是些你一定會(huì)經(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壓縮
- 中級(jí)(這些是你可能在一些相對(duì)復(fù)雜情況下可能用到的)
- 9.重用和延遲加載Views
- 10.Cache, Cache, 還是Cache咬崔!
- 11.權(quán)衡渲染方法
- 12.處理內(nèi)存警告
- 13.重用大開銷的對(duì)象
- 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ù)存儲(chǔ)選項(xiàng)
- 進(jìn)階級(jí)(這些建議只應(yīng)該在你確信他們可以解決問(wèn)題和得心應(yīng)手的情況下采用)
- 22.加速啟動(dòng)時(shí)間
- 23.使用Autorelease Pool
- 24.選擇是否緩存圖片
- 25.盡量避免日期格式轉(zhuǎn)換
初學(xué)者性能提升
這個(gè)部分致力于一些能提高性能的基本改變狸棍。但所有層次的開發(fā)者都有可能會(huì)從這個(gè)記錄了一些被忽視的項(xiàng)目的小小的性能備忘錄里獲得一些提升。
1. 用ARC管理內(nèi)存
ARC(Automatic Reference Counting, 自動(dòng)引用計(jì)數(shù))和iOS5一起發(fā)布赞枕,它避免了最常見的也就是經(jīng)常是由于我們忘記釋放內(nèi)存所造成的內(nèi)存泄露澈缺。它自動(dòng)為你管理retain和release的過(guò)程,所以你就不必去手動(dòng)干預(yù)了炕婶。
下面是你會(huì)經(jīng)常用來(lái)去創(chuàng)建一個(gè)View的代碼段:
UIView *view = [[UIView alloc] init];
// ...
[self.view addSubview:view];
[view release];
忘掉代碼段結(jié)尾的release簡(jiǎn)直像記得吃飯一樣簡(jiǎn)單姐赡。而ARC會(huì)自動(dòng)在底層為你做這些工作。
除了幫你避免內(nèi)存泄露柠掂,ARC還可以幫你提高性能项滑,它能保證釋放掉不再需要的對(duì)象的內(nèi)存。這都啥年代了涯贞,你應(yīng)該在你的所有項(xià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不能做到以及我們?cè)撛趺醋龅奈恼?http://conradstoll.com/blog/2013/1/19/blocks-operations-and-retain-cycles.html宋渔。
一個(gè)開發(fā)中常見的錯(cuò)誤就是沒有給UITableViewCells州疾, UICollectionViewCells,甚至是UITableViewHeaderFooterViews設(shè)置正確的reuseIdentifier皇拣。
為了性能最優(yōu)化严蓖,table view用
tableView:cellForRowAtIndexPath:
為rows分配cells的時(shí)候,它的數(shù)據(jù)應(yīng)該重用自UITableViewCell氧急。 一個(gè)table view維持一個(gè)隊(duì)列的數(shù)據(jù)可重用的UITableViewCell對(duì)象颗胡。不使用reuseIdentifier的話,每顯示一行table view就不得不設(shè)置全新的cell吩坝。這對(duì)性能的影響可是相當(dāng)大的杭措,尤其會(huì)使app的滾動(dòng)體驗(yàn)大打折扣。
自iOS6起钾恢,除了UICollectionView的cells和補(bǔ)充views手素,你也應(yīng)該在header和footer views中使用reuseIdentifiers鸳址。
想要使用reuseIdentifiers的話,在一個(gè)table view中添加一個(gè)新的cell時(shí)在data source object中添加這個(gè)方法:
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
這個(gè)方法把那些已經(jīng)存在的cell從隊(duì)列中排除泉懦,或者在必要時(shí)使用先前注冊(cè)的nib或者class創(chuàng)造新的cell稿黍。如果沒有可重用的cell,你也沒有注冊(cè)一個(gè)class或者nib的話崩哩,這個(gè)方法返回nil巡球。
3.盡量把views設(shè)置為不透明
如果你有不透明的Views,你應(yīng)該設(shè)置它們的opaque屬性為YES邓嘹。
原因是這會(huì)使系統(tǒng)用一個(gè)最優(yōu)的方式渲染這些views酣栈。這個(gè)簡(jiǎn)單的屬性在IB或者代碼里都可以設(shè)定。
Apple的文檔對(duì)于為圖片設(shè)置不透明屬性的描述是:
(opaque)這個(gè)屬性給渲染系統(tǒng)提供了一個(gè)如何處理這個(gè)view的提示汹押。如果設(shè)為YES矿筝, 渲染系統(tǒng)就認(rèn)為這個(gè)view是完全不透明的,這使得渲染系統(tǒng)優(yōu)化一些渲染過(guò)程和提高性能棚贾。如果設(shè)置為NO窖维,渲染系統(tǒng)正常地和其它內(nèi)容組成這個(gè)View。默認(rèn)值是YES妙痹。
在相對(duì)比較靜止的畫面中铸史,設(shè)置這個(gè)屬性不會(huì)有太大影響。然而當(dāng)這個(gè)view嵌在scroll view里邊怯伊,或者是一個(gè)復(fù)雜動(dòng)畫的一部分琳轿,不設(shè)置這個(gè)屬性的話會(huì)在很大程度上影響app的性能。
你可以在模擬器中用Debug\Color Blended Layers選項(xiàng)來(lái)發(fā)現(xiàn)哪些view沒有被設(shè)置為opaque耿芹。目標(biāo)就是利赋,能設(shè)為opaque的就全設(shè)為opaque!
4. 避免過(guò)于龐大的XIB
iOS5中加入的Storyboards(分鏡)正在快速取代XIB。然而XIB在一些場(chǎng)景中仍然很有用猩系。比如你的app需要適應(yīng)iOS5之前的設(shè)備媚送,或者你有一個(gè)自定義的可重用的view,你就不可避免地要用到他們。
如果你不得不XIB的話寇甸,使他們盡量簡(jiǎn)單塘偎。嘗試為每個(gè)Controller配置一個(gè)單獨(dú)的XIB,盡可能把一個(gè)View Controller的view層次結(jié)構(gòu)分散到單獨(dú)的XIB中去拿霉。
需要注意的是吟秩,當(dāng)你加載一個(gè)XIB的時(shí)候所有內(nèi)容都被放在了內(nèi)存里,包括任何圖片绽淘。如果有一個(gè)不會(huì)即刻用到的view涵防,你這就是在浪費(fèi)寶貴的內(nèi)存資源了。Storyboards就是另一碼事兒了沪铭,storyboard僅在需要時(shí)實(shí)例化一個(gè)view controller.
當(dāng)家在XIB是壮池,所有圖片都被chache偏瓤,如果你在做OS X開發(fā)的話,聲音文件也是椰憋。Apple在相關(guān)文檔中的記述是:
當(dāng)你加載一個(gè)引用了圖片或者聲音資源的nib時(shí)厅克,nib加載代碼會(huì)把圖片和聲音文件寫進(jìn)內(nèi)存。在OS X中橙依,圖片和聲音資源被緩存在named cache中以便將來(lái)用到時(shí)獲取证舟。在iOS中,僅圖片資源會(huì)被存進(jìn)named caches窗骑。取決于你所在的平臺(tái)女责,使用NSImage 或UIImage 的imageNamed:
方法來(lái)獲取圖片資源。
很明顯创译,同樣的事情也發(fā)生在storyboards中抵知,但我并沒有找到任何支持這個(gè)結(jié)論的文檔。如果你了解這個(gè)操作昔榴,寫信給我!
想要了解更多關(guān)于storyboards的內(nèi)容的話你可以看看 Matthijs Hollemans的Beginning Storyboards in iOS 5 Part 1 和 Part 2
永遠(yuǎn)不要使主線程承擔(dān)過(guò)多碘橘。因?yàn)閁IKit在主線程上做所有工作互订,渲染,管理觸摸反應(yīng)痘拆,回應(yīng)輸入等都需要在它上面完成仰禽。
一直使用主線程的風(fēng)險(xiǎn)就是如果你的代碼真的block了主線程,你的app會(huì)失去反應(yīng)纺蛆。這吐葵。。桥氏。正是在App Store中拿到一顆星的捷徑 :]
大部分阻礙主進(jìn)程的情形是你的app在做一些牽涉到讀寫外部資源的I/O操作温峭,比如存儲(chǔ)或者網(wǎng)絡(luò)。
你可以使用
NSURLConnection
異步地做網(wǎng)絡(luò)操作:+ (void)sendAsynchronousRequest:(NSURLRequest *)request queue:(NSOperationQueue *)queue completionHandler:(void (^)(NSURLResponse*, NSData*, NSError*))handler
或者使用像 AFNetworking這樣的框架來(lái)異步地做這些操作字支。
如果你需要做其它類型的需要耗費(fèi)巨大資源的操作(比如時(shí)間敏感的計(jì)算或者存儲(chǔ)讀寫)那就用 Grand Central Dispatch凤藏,或者 NSOperation 和 NSOperationQueues.
下面代碼是使用GCD的模板:
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)代碼中有一個(gè)嵌套的dispatch_async
嗎?這是因?yàn)槿魏蜺IKit相關(guān)的代碼需要在主線程上進(jìn)行堕伪。
如果你對(duì) NSOperation 或者GCD 的細(xì)節(jié)感興趣的話揖庄,看看Ray Wenderlich的 Multithreading and Grand Central Dispatch on iOS for Beginners, 還有 Soheil Azarpour 的 How To Use NSOperations and NSOperationQueues 教程欠雌。
如果要在
UIImageView
中顯示一個(gè)來(lái)自bundle的圖片蹄梢,你應(yīng)保證圖片的大小和UIImageView的大小相同。在運(yùn)行中縮放圖片是很耗費(fèi)資源的富俄,特別是UIImageView
嵌套在UIScrollView
中的情況下禁炒。
如果圖片是從遠(yuǎn)端服務(wù)加載的你不能控制圖片大小而咆,比如在下載前調(diào)整到合適大小的話,你可以在下載完成后齐苛,最好是用background thread翘盖,縮放一次,然后在UIImageView中使用縮放后的圖片凹蜂。
學(xué)會(huì)選擇對(duì)業(yè)務(wù)場(chǎng)景最合適的類或者對(duì)象是寫出能效高的代碼的基礎(chǔ)馍驯。當(dāng)處理collections時(shí)這句話尤其正確。
Apple有一個(gè) Collections Programming Topics 的文檔詳盡介紹了可用的classes間的差別和你該在哪些場(chǎng)景中使用它們玛痊。這對(duì)于任何使用collections的人來(lái)說(shuō)是一個(gè)必讀的文檔汰瘫。
呵呵,我就知道你因?yàn)樘L(zhǎng)沒看…這是一些常見collection的總結(jié):
- Arrays: 有序的一組值擂煞。使用index來(lái)lookup很快混弥,使用value lookup很慢, 插入/刪除很慢对省。
- Dictionaries: 存儲(chǔ)鍵值對(duì)蝗拿。 用鍵來(lái)查找比較快。
- Sets: 無(wú)序的一組值蒿涎。用值來(lái)查找很快哀托,插入/刪除很快。
8. 打開gzip壓縮
大量app依賴于遠(yuǎn)端資源和第三方API劳秋,你可能會(huì)開發(fā)一個(gè)需要從遠(yuǎn)端下載XML, JSON, HTML或者其它格式的app仓手。
問(wèn)題是我們的目標(biāo)是移動(dòng)設(shè)備,因此你就不能指望網(wǎng)絡(luò)狀況有多好玻淑。一個(gè)用戶現(xiàn)在還在edge網(wǎng)絡(luò)嗽冒,下一分鐘可能就切換到了3G。不論什么場(chǎng)景补履,你肯定不想讓你的用戶等太長(zhǎng)時(shí)間添坊。
減小文檔的一個(gè)方式就是在服務(wù)端和你的app中打開gzip。這對(duì)于文字這種能有更高壓縮率的數(shù)據(jù)來(lái)說(shuō)會(huì)有更顯著的效用箫锤。
好消息是帅腌,iOS已經(jīng)在NSURLConnection中默認(rèn)支持了gzip壓縮,當(dāng)然AFNetworking這些基于它的框架亦然麻汰。像Google App Engine這些云服務(wù)提供者也已經(jīng)支持了壓縮輸出速客。
如果你不知道如何利用Apache或者IIS(服務(wù)器)來(lái)打開gzip,可以讀下這篇文章五鲫。
中級(jí)性能提升
你確信你已經(jīng)掌握了前述那些基礎(chǔ)級(jí)的優(yōu)化方案了嗎溺职?但實(shí)際情況是,有時(shí)一些解決方案并不像那些一樣明顯,它們往往嚴(yán)重依賴于你如何架構(gòu)和書寫你的app浪耘。下面的這些建議就是針對(duì)這些場(chǎng)景的乱灵。
9. 重用和延遲加載(lazy load) Views
更多的view意味著更多的渲染,也就是更多的CPU和內(nèi)存消耗七冲,對(duì)于那種嵌套了很多view在UIScrollView里邊的app更是如此痛倚。
這里我們用到的技巧就是模仿UITableView
和UICollectionView
的操作: 不要一次創(chuàng)建所有的subview,而是當(dāng)需要時(shí)才創(chuàng)建澜躺,當(dāng)它們完成了使命蝉稳,把他們放進(jìn)一個(gè)可重用的隊(duì)列中。
這樣的話你就只需要在滾動(dòng)發(fā)生時(shí)創(chuàng)建你的views掘鄙,避免了不劃算的內(nèi)存分配耘戚。
創(chuàng)建views的能效問(wèn)題也適用于你app的其它方面。想象一下一個(gè)用戶點(diǎn)擊一個(gè)按鈕的時(shí)候需要呈現(xiàn)一個(gè)view的場(chǎng)景操漠。有兩種實(shí)現(xiàn)方法:
- 1.創(chuàng)建并隱藏這個(gè)view當(dāng)這個(gè)screen加載的時(shí)候收津,當(dāng)需要時(shí)顯示它;
- 2.當(dāng)需要時(shí)才創(chuàng)建并展示浊伙。
每個(gè)方案都有其優(yōu)缺點(diǎn):
用第一種方案的話因?yàn)槟阈枰婚_始就創(chuàng)建一個(gè)view并保持它直到不再使用撞秋,這就會(huì)更加消耗內(nèi)存。然而這也會(huì)使你的app操作更敏感因?yàn)楫?dāng)用戶點(diǎn)擊按鈕的時(shí)候它只需要改變一下這個(gè)view的可見性嚣鄙。
第二種方案則相反-消耗更少內(nèi)存吻贿,但是會(huì)在點(diǎn)擊按鈕的時(shí)候比第一種稍顯卡頓。
10. Cache, Cache, 還是Cache!
一個(gè)極好的原則就是拗慨,緩存所需要的廓八,也就是那些不大可能改變但是需要經(jīng)常讀取的東西奉芦。
我們能緩存些什么呢赵抢?一些選項(xiàng)是,遠(yuǎn)端服務(wù)器的響應(yīng)声功,圖片烦却,甚至計(jì)算結(jié)果,比如UITableView的行高先巴。
NSURLConnection默認(rèn)會(huì)緩存資源在內(nèi)存或者存儲(chǔ)中根據(jù)它所加載的HTTP Headers其爵。你甚至可以手動(dòng)創(chuàng)建一個(gè)NSURLRequest然后使它只加載緩存的值。
下面是一個(gè)可用的代碼段伸蚯,你可以可以用它去為一個(gè)基本不會(huì)改變的圖片創(chuàng)建一個(gè)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;
}
注意你可以通過(guò) NSURLConnection 獲取一個(gè)URL request摩渺, AFNetworking也一樣的。這樣你就不必為采用這條tip而改變所有的networking代碼了剂邮。
如果想了解更多關(guān)于HTTP caching, NSURLCache, NSURLConnection的相關(guān)知識(shí)摇幻,可以讀下這篇文章。
如果你需要緩存其它不是HTTP Request的東西,你可以用NSCache绰姻。
NSCache和NSDictionary類似枉侧,不同的是系統(tǒng)回收內(nèi)存的時(shí)候它會(huì)自動(dòng)刪掉它的內(nèi)容。 Mattt Thompson有一篇很棒的關(guān)于它的文章::http://nshipster.com/nscache/
如果你對(duì)HTTP感興趣可以讀下Google的這篇 best-practices document on HTTP caching狂芋。
11. 權(quán)衡渲染方法
在iOS中可以有很多方法做出漂亮的按鈕榨馁。你可以用整幅的圖片,可調(diào)大小的圖片帜矾,uozhe可以用CALayer翼虫, CoreGraphics甚至OpenGL來(lái)畫它們。
當(dāng)然每個(gè)不同的解決方法都有不同的復(fù)雜程度和相應(yīng)的性能黍特。有一篇Apple UIKit team中的一員Andy Matuschak推薦過(guò)的很棒的關(guān)于graphic性能的帖子很值得一讀蛙讥。
簡(jiǎn)單來(lái)說(shuō),就是用事先渲染好的圖片更快一些灭衷,因?yàn)槿绱艘粊?lái)iOS就免去了創(chuàng)建一個(gè)圖片再畫東西上去然后顯示在屏幕上的程序次慢。問(wèn)題是你需要把所有你需要用到的圖片放到app的bundle里面,這樣就增加了體積 – 這就是使用可變大小的圖片更好的地方了: 你可以省去一些不必要的空間翔曲,也不需要再為不同的元素(比如按鈕)來(lái)做不同的圖迫像。
然而,使用圖片也意味著你失去了使用代碼調(diào)整圖片的機(jī)動(dòng)性瞳遍,你需要一遍又一遍不斷地重做他們闻妓,這樣就很浪費(fèi)時(shí)間了,而且你如果要做一個(gè)動(dòng)畫效果掠械,雖然每幅圖只是一些細(xì)節(jié)的變化你就需要很多的圖片造成bundle大小的不斷增大由缆。
總得來(lái)說(shuō),你需要權(quán)衡一下利弊,到底是要性能能還是要bundle保持合適的大小。
12. 處理內(nèi)存警告
一旦系統(tǒng)內(nèi)存過(guò)低折剃,iOS會(huì)通知所有運(yùn)行中app。在官方文檔中是這樣記述:
如果你的app收到了內(nèi)存警告,它就需要盡可能釋放更多的內(nèi)存层扶。最佳方式是移除對(duì)緩存镜会,圖片object和其他一些可以重創(chuàng)建的objects的strong references.
幸運(yùn)的是,UIKit提供了幾種收集低內(nèi)存警告的方法:
- 在app delegate中使用
applicationDidReceiveMemoryWarning:
的方法 - 在你的自定義UIViewController的子類(subclass)中覆蓋
didReceiveMemoryWarning
- 注冊(cè)并接收 UIApplicationDidReceiveMemoryWarningNotification 的通知
一旦收到這類通知终抽,你就需要釋放任何不必要的內(nèi)存使用戳表。
例如焰薄,UIViewController的默認(rèn)行為是移除一些不可見的view, 它的一些子類則可以補(bǔ)充這個(gè)方法扒袖,刪掉一些額外的數(shù)據(jù)結(jié)構(gòu)塞茅。一個(gè)有圖片緩存的app可以移除不在屏幕上顯示的圖片。
這樣對(duì)內(nèi)存警報(bào)的處理是很必要的季率,若不重視野瘦,你的app就可能被系統(tǒng)殺掉。
然而飒泻,當(dāng)你一定要確認(rèn)你所選擇的object是可以被重現(xiàn)創(chuàng)建的來(lái)釋放內(nèi)存鞭光。一定要在開發(fā)中用模擬器中的內(nèi)存提醒模擬去測(cè)試一下。
一些objects的初始化很慢泞遗,比如NSDateFormatter和NSCalendar惰许。然而,你又不可避免地需要使用它們史辙,比如從JSON或者XML中解析數(shù)據(jù)汹买。
想要避免使用這個(gè)對(duì)象的瓶頸你就需要重用他們,可以通過(guò)添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來(lái)實(shí)現(xiàn)聊倔。
注意如果你要選擇第二種方法晦毙,對(duì)象會(huì)在你的app運(yùn)行時(shí)一直存在于內(nèi)存中,和單例(singleton)很相似耙蔑。
下面的代碼說(shuō)明了使用一個(gè)屬性來(lái)延遲加載一個(gè)date formatter. 第一次調(diào)用時(shí)它會(huì)創(chuàng)建一個(gè)新的實(shí)例见妒,以后的調(diào)用則將返回已經(jīng)創(chuàng)建的實(shí)例:
// 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í)設(shè)置一個(gè)NSDateFormatter的速度差不多是和創(chuàng)建新的一樣慢的须揣!所以如果你的app需要經(jīng)常進(jìn)行日期格式處理的話,你會(huì)從這個(gè)方法中得到不小的性能提升寥院。
14. 使用Sprite Sheets
你是一個(gè)游戲開發(fā)者嗎秸谢,那么Sprite sheets一定是一個(gè)你的最好的朋友了估蹄。Sprite sheet可以讓渲染速度加快,甚至比標(biāo)準(zhǔn)的屏幕渲染方法節(jié)省內(nèi)存。
我們有兩個(gè)很好的關(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
第二個(gè)教程涵蓋了可能在很大程度上影響你游戲性能的pixel格式的細(xì)節(jié)。
如果你對(duì)于spirte sheet還不是很熟悉系枪,可以看下這兩個(gè)(youtube)視頻SpriteSheets – The Movie, Part 1 和 Part 2雾棺。視頻的作者是創(chuàng)建Sprite sheet很流行的工具之一Texture Packer的作者Andreas Low捌浩。
除了使用Sprite sheets尸饺,其它寫在這里的建議當(dāng)然也可以用于游戲開發(fā)中侵佃。比如你需要很多的Sprite sheets,像敵人倍谜,導(dǎo)彈之類的動(dòng)作類必備元素,你可以重用這些sprites而不用每次都要重新創(chuàng)建答毫。
15. 避免反復(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ù)來(lái)展示一個(gè)table view,最好直接從服務(wù)器取array結(jié)構(gòu)的數(shù)據(jù)以避免額外的中間數(shù)據(jù)結(jié)構(gòu)改變嘉涌。
類似的仑最,如果需要從特定key中取數(shù)據(jù)警医,那么就使用鍵值對(duì)的dictionary损敷。
16. 選擇正確的數(shù)據(jù)格式
從app和網(wǎng)絡(luò)服務(wù)間傳輸數(shù)據(jù)有很多方案拗馒,最常見的就是JSON和XML诱桂。你需要選擇對(duì)你的app來(lái)說(shuō)最合適的一個(gè)挥等。
解析JSON會(huì)比XML更快一些,JSON也通常更小更便于傳輸辞槐。從iOS5起有了官方內(nèi)建的JSON deserialization 就更加方便使用了榄檬。
但是XML也有XML的好處鹿榜,比如使用SAX 來(lái)解析XML就像解析本地文件一樣舱殿,你不需像解析json一樣等到整個(gè)文檔下載完成才開始解析刺彩。當(dāng)你處理很大的數(shù)據(jù)的時(shí)候就會(huì)極大地減低內(nèi)存消耗和增加性能。
17. 正確設(shè)定背景圖片
在View里放背景圖片就像很多其它iOS編程一樣有很多方法:
使用UIColor的colorWithPatternImage
來(lái)設(shè)置背景色焚碌;
在view中添加一個(gè)UIImageView作為一個(gè)子View十电。
如果你使用全畫幅的背景圖鹃骂,你就必須使用UIImageView因?yàn)閁IColor的colorWithPatternImage
是用來(lái)創(chuàng)建小的重復(fù)的圖片作為背景的畏线。這種情形下使用UIImageView可以節(jié)約不少的內(nèi)存:
// You could also achieve the same result in Interface Builder
UIImageView *backgroundView = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"background"]];
[self.view addSubview:backgroundView];
如果你用小圖平鋪來(lái)創(chuàng)建背景,你就需要用UIColor的colorWithPatternImage來(lái)做了蚣常,它會(huì)更快地渲染也不會(huì)花費(fèi)很多內(nèi)存:
self.view.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:@"background"]];
18. 減少使用Web特性
UIWebView很有用抵蚊,用它來(lái)展示網(wǎng)頁(yè)內(nèi)容或者創(chuàng)建UIKit很難做到的動(dòng)畫效果是很簡(jiǎn)單的一件事泌射。
但是你可能有注意到UIWebView并不像驅(qū)動(dòng)Safari的那么快。這是由于以JIT compilation 為特色的Webkit的Nitro Engine的限制豺裆。
所以想要更高的性能你就要調(diào)整下你的HTML了躺酒。第一件要做的事就是盡可能移除不必要的javascript羹应,避免使用過(guò)大的框架园匹。能只用原生js就更好了裸违。
另外,盡可能異步加載例如用戶行為統(tǒng)計(jì)script這種不影響頁(yè)面表達(dá)的javascript枪汪。
最后雀久,永遠(yuǎn)要注意你使用的圖片,保證圖片的符合你使用的大小巡蘸。使用Sprite sheet提高加載速度和節(jié)約內(nèi)存悦荒。
更多相關(guān)信息可以看下 WWDC 2012 session #601 – Optimizing Web Content in UIWebViews and Websites on iOS
19. 設(shè)定Shadow Path
如何在一個(gè)View或者一個(gè)layer上加一個(gè)shadow呢搬味,QuartzCore框架是很多開發(fā)者的選擇:
#import
// 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;
看起來(lái)很簡(jiǎn)單,對(duì)吧悦析。
可是强戴,壞消息是使用這個(gè)方法也有它的問(wèn)題… Core Animation不得不先在后臺(tái)得出你的圖形并加好陰影然后才渲染骑歹,這開銷是很大的道媚。
使用shadowPath的話就避免了這個(gè)問(wèn)題:
view.layer.shadowPath = [[UIBezierPath bezierPathWithRect:view.bounds] CGPath];
使用shadow path的話iOS就不必每次都計(jì)算如何渲染谴分,它使用一個(gè)預(yù)先計(jì)算好的路徑狸剃。但問(wèn)題是自己計(jì)算path的話可能在某些View中比較困難,且每當(dāng)view的frame變化的時(shí)候你都需要去update shadow path.
想了解更多可以看看Mark Pospesel的這篇匿刮。
20. 優(yōu)化Table View
Table view需要有很好的滾動(dòng)性能熟丸,不然用戶會(huì)在滾動(dòng)過(guò)程中發(fā)現(xiàn)動(dòng)畫的瑕疵。
為了保證table view平滑滾動(dòng)纱兑,確保你采取了以下的措施:
- 正確使用
reuseIdentifier
來(lái)重用cells - 盡量使所有的view opaque潜慎,包括cell自身
- 避免漸變铐炫,圖片縮放倒信,后臺(tái)選人
- 緩存行高
- 如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來(lái)自web,使用異步加載竞穷,緩存請(qǐng)求結(jié)果
- 使用
shadowPath
來(lái)畫陰影 - 減少subviews的數(shù)量
- 盡量不適用
cellForRowAtIndexPath:
瘾带,如果你需要用到它看政,只用一次然后緩存結(jié)果 - 使用正確的數(shù)據(jù)結(jié)構(gòu)來(lái)存儲(chǔ)數(shù)據(jù)
- 使用
rowHeight
,sectionFooterHeight
和sectionHeaderHeight
來(lái)設(shè)定固定的高于颖,不要請(qǐng)求delegate
21. 選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)
當(dāng)存儲(chǔ)大塊數(shù)據(jù)時(shí)你會(huì)怎么做森渐?
你有很多選擇同衣,比如:
- 使用
NSUerDefaults
- 使用XML, JSON, 或者 plist
- 使用NSCoding存檔
- 使用類似SQLite的本地SQL數(shù)據(jù)庫(kù)
- 使用 Core Data
NSUserDefaults的問(wèn)題是什么?雖然它很nice也很便捷蒋情,但是它只適用于小數(shù)據(jù)辕翰,比如一些簡(jiǎn)單的布爾型的設(shè)置選項(xiàng)金蜀,再大點(diǎn)你就要考慮其它方式了
XML這種結(jié)構(gòu)化檔案呢渊抄?總體來(lái)說(shuō)护桦,你需要讀取整個(gè)文件到內(nèi)存里去解析二庵,這樣是很不經(jīng)濟(jì)的催享。使用SAX又是一個(gè)很麻煩的事情因妙。
NSCoding铣耘?不幸的是蜗细,它也需要讀寫文件炉媒,所以也有以上問(wèn)題橱野。
在這種應(yīng)用場(chǎng)景下,使用SQLite 或者 Core Data比較好茅郎。使用這些技術(shù)你用特定的查詢語(yǔ)句就能只加載你需要的對(duì)象系冗。
在性能層面來(lái)講薪鹦,SQLite和Core Data是很相似的奔害。他們的不同在于具體使用方法华临。Core Data代表一個(gè)對(duì)象的graph model雅潭,但SQLite就是一個(gè)DBMS。Apple在一般情況下建議使用Core Data椿浓,但是如果你有理由不使用它轰绵,那么就去使用更加底層的SQLite吧左腔。
如果你使用SQLite液样,你可以用FMDB(https://github.com/ccgus/fmdb)這個(gè)庫(kù)來(lái)簡(jiǎn)化SQLite的操作,這樣你就不用花很多經(jīng)歷了解SQLite的C API了坊秸。
進(jìn)階性能提示
想要一些是你成為程序猿忍者的精英級(jí)的建議嗎褒搔?下面這些提示可以幫你把你的app優(yōu)化到極致星瘾!
22. 加速啟動(dòng)時(shí)間
快速打開app是很重要的,特別是用戶第一次打開它時(shí)念逞,對(duì)app來(lái)講翎承,第一印象太太太重要了。
你能做的就是使它盡可能做更多的異步任務(wù)待讳,比如加載遠(yuǎn)端或者數(shù)據(jù)庫(kù)數(shù)據(jù)痴晦,解析數(shù)據(jù)誊酌。
還是那句話涂邀,避免過(guò)于龐大的XIB比勉,因?yàn)樗麄兪窃谥骶€程上加載的浩聋。所以盡量使用沒有這個(gè)問(wèn)題的Storyboards吧!
注意坊夫,用Xcode debug時(shí)watchdog并不運(yùn)行厂画,一定要把設(shè)備從Xcode斷開來(lái)測(cè)試啟動(dòng)速度
23. 使用Autorelease Pool
NSAutoreleasePool
負(fù)責(zé)釋放block中的autoreleased objects。一般情況下它會(huì)自動(dòng)被UIKit調(diào)用屎慢。但是有些狀況下你也需要手動(dòng)去創(chuàng)建它腻惠。
假如你創(chuàng)建很多臨時(shí)對(duì)象,你會(huì)發(fā)現(xiàn)內(nèi)存一直在減少直到這些對(duì)象被release的時(shí)候欣喧。這是因?yàn)橹挥挟?dāng)UIKit用光了autorelease pool的時(shí)候memory才會(huì)被釋放。
好消息是你可以在你自己的@autoreleasepool里創(chuàng)建臨時(shí)的對(duì)象來(lái)避免這個(gè)行為:
NSArray *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對(duì)象
更多關(guān)于NSAutoreleasePool請(qǐng)參考官方文檔闲询。
24. 選擇是否緩存圖片
常見的從bundle中加載圖片的方式有兩種扭弧,一個(gè)是用imageNamed
,二是用imageWithContentsOfFile
泊愧,第一種比較常見一點(diǎn)。
既然有兩種類似的方法來(lái)實(shí)現(xiàn)相同的目的痰滋,那么他們之間的差別是什么呢敲街?
imageNamed
的優(yōu)點(diǎn)是當(dāng)加載時(shí)會(huì)緩存圖片。 imageNamed
的文檔中這么說(shuō):
這個(gè)方法用一個(gè)指定的名字在系統(tǒng)緩存中查找并返回一個(gè)圖片對(duì)象如果它存在的話。如果緩存中沒有找到相應(yīng)的圖片拨匆,這個(gè)方法從指定的文檔中加載然后緩存并返回這個(gè)對(duì)象姆涩。
相反的,imageWithContentsOfFile
僅加載圖片惭每。
下面的代碼說(shuō)明了這兩種方法的用法:
UIImage *img = [UIImage imageNamed:@"myImage"];
// caching
// or
UIImage *img = [UIImage imageWithContentsOfFile:@"myImage"];
// no caching
那么我們應(yīng)該如何選擇呢骨饿?
如果你要加載一個(gè)大圖片而且是一次性使用,那么就沒必要緩存這個(gè)圖片台腥,用imageWithContentsOfFile
足矣宏赘,這樣不會(huì)浪費(fèi)內(nèi)存來(lái)緩存它。
然而览爵,在圖片反復(fù)重用的情況下imageNamed
是一個(gè)好得多的選擇。
25. 避免日期格式轉(zhuǎn)換
如果你要用NSDateFormatter
來(lái)處理很多日期格式,應(yīng)該小心以待。就像先前提到的,任何時(shí)候重用NSDateFormatters
都是一個(gè)好的實(shí)踐。
然而,如果你需要更多速度坑傅,那么直接用C是一個(gè)好的方案。Sam Soffes有一個(gè)不錯(cuò)的帖子(http://soff.es/how-to-drastically-improve-your-app-with-an-afternoon-and-instruments)里面有一些可以用來(lái)解析ISO-8601日期字符串的代碼,簡(jiǎn)單重寫一下就可以拿來(lái)用了。
嗯幌甘,直接用C來(lái)搞,看起來(lái)不錯(cuò)了,但是你相信嗎恒界,我們還有更好的方案婆誓!
如果你可以控制你所處理的日期格式文留,盡量選擇Unix時(shí)間戳靶端。你可以方便地從時(shí)間戳轉(zhuǎn)換到NSDate:
- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp {
return [NSDate dateWithTimeIntervalSince1970:timestamp];
}
這樣會(huì)比用C來(lái)解析日期字符串還快吁断!
需要注意的是,許多web API會(huì)以微秒的形式返回時(shí)間戳乘盼,因?yàn)檫@種格式在javascript中更方便使用蓖柔。記住用dateFromUnixTimestamp
之前除以1000就好了。
本文參考自:iOS應(yīng)用性能調(diào)優(yōu)的25個(gè)建議和技巧
更多 性能優(yōu)化的文章:
iOS性能優(yōu)化 ----- Instruments
iOS 性能調(diào)優(yōu),成為一名合格iOS程序員必須掌握的技能 ----- InstrumentsiOS app性能優(yōu)化的那些事
??iOS應(yīng)用性能調(diào)優(yōu)的25個(gè)建議和技巧