iOS App性能優(yōu)化技巧

? ? ? 性能對(duì) iOS 應(yīng)用的開發(fā)尤其重要,如果你的應(yīng)用失去反應(yīng)或者很慢,那么App Store的評(píng)論顯而易見。然而由于iOS設(shè)備的限制,有時(shí)搞好性能是一件難事。開發(fā)過程中你會(huì)有很多需要注意的事項(xiàng),你也很容易在做出選擇時(shí)忘記考慮性能影響。

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

1. 用ARC管理內(nèi)存,靜態(tài)分析product ------>? Anlyze 或者快捷鍵 shift+com+B,凡是遇到 retain 、copy 、create 出的對(duì)象, 都需要進(jìn)行 release.例如創(chuàng)建繪制路徑CGPathCreateMutable()就需要釋放CGPathRelease(path),而屬于CoreFoundation另外一種釋放方式CFRelease(path).CGXxxxxCreate 對(duì)應(yīng)的就有 CGXxxxxRelease,通過 CFRelease(任何類型);可以釋放任何類型。

2. 在正確的地方使用reuseIdentifier重用cells。

3. 盡可能使Views不透明,盡量使所有的view opaque,包括cell自身.

4. 避免龐大的XIB, xib官方解釋: xib是XML文件定義和配置的一組對(duì)象(Xib是XML格式的文件),并專門操縱主要觀點(diǎn)(UIView子類)。Xcode具有友好的編輯器,可以顯示這些意見,它是一個(gè)運(yùn)行的應(yīng)用程序,使得它的配置和設(shè)計(jì)布局非常容易(節(jié)省很多行代碼)。xib優(yōu)勢:相比純代碼,大大縮短了UI界面搭建的時(shí)間,提高了開發(fā)效率,可視化的效果,更直觀的設(shè)計(jì),在版本管理上和純代碼的差異并不是很大,易讀易維護(hù)。問題: Xib無法進(jìn)行邏輯判斷,很難在運(yùn)行時(shí)進(jìn)行配置,Xib在使用時(shí),經(jīng)常要通過代碼的補(bǔ)充,來完成功能實(shí)現(xiàn)。多人開發(fā)中通過代碼修改Xib的屬性,可能造成混亂和不可預(yù)計(jì)的問題。從程序員角度,可讀性較差。不利于統(tǒng)一管理和維護(hù)。使用SVN等代碼管理工具時(shí),Xib會(huì)產(chǎn)生無用的記錄,以及版本更新的缺陷。從Xcode5開始,蘋果對(duì)這一方面問題進(jìn)行了優(yōu)化,比如在版本管理上,也可以很好的查找修改記錄了。xib中的設(shè)置往往并非最終設(shè)置,UI設(shè)計(jì)會(huì)被代碼所覆蓋。

5. 不要block操作主線程,在執(zhí)行 block 之前,首先會(huì)尋找合適的線程來執(zhí)行block,然后阻塞這個(gè)線程,直到 block 執(zhí)行完畢,尋找線程的規(guī)則是: 任何提交到主隊(duì)列的 block 都會(huì)在主線程中執(zhí)行,在不違背此規(guī)則的前提下,盡可能的在當(dāng)前線程執(zhí)行 block。在主線程中執(zhí)行的代碼,也很可能不是運(yùn)行在主隊(duì)列中(反之也是)。

6. 在ImageViews中調(diào)整圖片大小,例如: imageView.clipsToBounds = YES; ?[imageView.layer setCornerRadius:50];

這樣設(shè)置會(huì)觸發(fā)離屏渲染,比較消耗性能。比如當(dāng)一個(gè)頁面上有十幾頭像這樣設(shè)置了圓角會(huì)明顯感覺到卡頓。注意:png圖片UIImageView處理圓角是不會(huì)產(chǎn)生離屏渲染的。(ios9.0之后不會(huì)離屏渲染,ios9.0之前還是會(huì)離屏渲染)。

7. 選擇正確的Collection,合理使用FlowLayout布局,使用代理方法重用cells。例如: 下圖中有多少個(gè)sections呢?

答案是2個(gè)。即我們?cè)趎umberOfSectionsInCollectionView:方法中返回2即可。

8. 打開gzip壓縮。服務(wù)端使用gzip壓縮,可以大幅度減小傳輸包的體積,加快客戶端網(wǎng)絡(luò)請(qǐng)求速度,為用戶節(jié)省流量。當(dāng)服務(wù)器返回的httpHeader的"Content-Encoding" 屬性的值是gzip時(shí),數(shù)據(jù)會(huì)自動(dòng)被解壓縮,但有時(shí)候在客戶端還沒拿到數(shù)據(jù)的時(shí)候,就已經(jīng)被某些網(wǎng)關(guān)解壓了,這樣gzip就沒有起到作用。因此可以約定其他策略,防止網(wǎng)關(guān)解壓,例如在別的頭屬性中標(biāo)記gzip. 如此,就需要我們自己來解壓gzip數(shù)據(jù)。方法如下:添加framework庫中的libbz2.1.0.dylib;給NSData添加- (NSData *)gzipUnpack方法解壓:并引入頭文件? #import "zlib.h"將拿到的data直接調(diào)用UnPack方法就完成解壓了。如果編譯出現(xiàn)link error,就到Target的設(shè)置,找到"Other Linker Flags"這一項(xiàng),添加-lz就可以了。

9. 重用和延遲加載Views。如果我們?cè)趩?dòng)程序時(shí),就把所有的views新建出來,等到用到時(shí)就加載。這樣的話,毫不疑問就會(huì)在內(nèi)存存放著許多的views,這樣明顯不行。系統(tǒng)的view默認(rèn)都是懶加載過程,只有用到view的時(shí)候,才會(huì)新建加載,節(jié)省CPU的消耗,在我們寫程序時(shí)也一定參考這種方法。

10. Cache使用。一種是NSUserDefaults,這個(gè)是應(yīng)用級(jí)別的cache、持久的不會(huì)隨程序的關(guān)閉、關(guān)機(jī)而消失,一般存儲(chǔ)應(yīng)用程序的配置信息、默認(rèn)數(shù)據(jù)等;不適合存儲(chǔ)業(yè)務(wù)數(shù)據(jù);數(shù)據(jù)量也不易過大。支持的存儲(chǔ)數(shù)據(jù)類型:NSData、NSString、NSNumber、NSDate、NSArray、NSDictionary。另一種是NSCache蘋果提供的一套緩存機(jī)制,和NSMutableDictionary(緩存池)使用起來相似, 線程安全,Mutable開發(fā)的類一般都是線程不安全的, 當(dāng)內(nèi)存不足時(shí)會(huì)自動(dòng)釋放內(nèi)存(所以從緩存中取數(shù)據(jù)的時(shí)候總要判斷是否為空) ,指定緩存的限額(countLimit),當(dāng)緩存超出限額自動(dòng)釋放內(nèi)存。

11. 權(quán)衡渲染方法。在iOS中隨處可見的不同的漂亮的按鈕,一般狀態(tài)下我們都是使用美工已經(jīng)切好的圖片來設(shè)置,同樣我們也可以用CALayer、CoreGraphics設(shè)置是OpenGL來完成這些功能。這兩種方法各有利弊,使用事先渲染過的圖片更快一些,省去了創(chuàng)建一個(gè)圖片接著進(jìn)行渲染最后顯示圖片的程序,但是我們需要將這些圖片放入app的bundle中等待著被使用,這回加大內(nèi)存空間的使用。通過渲染的方式可能會(huì)復(fù)雜一些但是當(dāng)遇到動(dòng)畫功能時(shí),渲染的優(yōu)勢就顯現(xiàn)出來,一般的動(dòng)畫我們可以通過圖片幀來完成,但是遇到復(fù)雜的圖片時(shí),就會(huì)顯心有余而力不足了。

12. 處理內(nèi)存警告。在 app delegate中使用applicationDidReceiveMemoryWarning:方法在自定義的控制器UIViewController子類中重寫父類的didReceiveMemoryWarning方法注冊(cè)接受UIApplicationDidReceiveMemoryWarningNotification的通知,一旦接受到通知就盡快釋放不用的內(nèi)存空間, 值得一說的是UIViewController默認(rèn)就會(huì)移除不可見的view,所以它的一些子類可以重寫相關(guān)的方法,刪除一些用不到的數(shù)據(jù)。

13. 重用大開銷的對(duì)象,這個(gè)就不用多說,例如cells之類。還比如NSDateFormatter和NSCalendar類的初始化非常慢,我們就通過使用屬性來延遲加載NSDateFormatter對(duì)象(懶加載)。

14. 使用Sprite Sheets, 主要用于游戲開發(fā)中,由于游戲肯定需要炫酷的畫面,使用Sprite sheets可以讓渲染的速度加快。同樣對(duì)于敵人、炮彈這些動(dòng)作類元素,你可以重用這些sprites而不用每次都要重新創(chuàng)建。

15. 避免反復(fù)處理數(shù)據(jù)。小量數(shù)據(jù)處理:NSKeyedArchiver/NSUserDefaults/Write寫入方式。大量數(shù)據(jù)SQLite/CoreData?;镜膒list適合Objective-C中內(nèi)置的數(shù)據(jù)類,要想存儲(chǔ)和讀取自定義的對(duì)象,需要使用歸檔(archive)和反歸檔(unarchiver). 嵌入式數(shù)據(jù)庫SQLite在處理大型數(shù)據(jù)時(shí)優(yōu)勢明顯。在不同的場合使用適當(dāng)?shù)姆椒?,是開發(fā)程序時(shí)的原則。

16. 選擇正確的數(shù)據(jù)格式。從網(wǎng)絡(luò)或者app傳輸數(shù)據(jù)常用到兩種格式:JSON、XML,這兩種方式也各有優(yōu)劣。解析JSON數(shù)據(jù)對(duì)比XML來說會(huì)更快一些,JSON也通常用于小數(shù)據(jù)的傳輸。對(duì)于XML數(shù)據(jù)來說,使用SAX來解析XML數(shù)據(jù)就像解析本地文件一樣,不必要解析JSON一樣等整個(gè)文件文檔下載完成后才開始解析。這樣當(dāng)你處理大數(shù)據(jù)時(shí)就會(huì)極大降低內(nèi)存消耗和提高性能。當(dāng)解析數(shù)據(jù)完成后,加載數(shù)據(jù)也需要注意,我們最好將數(shù)組數(shù)據(jù)轉(zhuǎn)化為模型數(shù)據(jù)裝入數(shù)組中,方便數(shù)據(jù)的展示。對(duì)于需要從特定的key中取數(shù)據(jù),那就使用鍵值對(duì)進(jìn)行操作。

17. 正確地設(shè)置BackgroundImages。在如下類進(jìn)行設(shè)置:UIBarButtonItem/UIButton/UINavigationBar/UISearchBar/UISegmentedControl/UITabBar/UIStepper/UIToolbar。

18. 減少使用Web特性。在app中UIWebView很有用,可以用來展示網(wǎng)頁內(nèi)容或者創(chuàng)建UIkit很難做到的動(dòng)畫效果。但是UIWebView的加載并不像我們想象的那么快,這是受web的一些特性的影響。因此想要提高性能就要調(diào)整HTML了,盡可能的移除不必要的JavaScript,避免使用過大的框架,使用原生js即可。我們要特別注意,我們要保證要使用的圖片符合你使用的大小,可以使用Sprite Sheets提高加載速度和節(jié)約內(nèi)容。

19. 設(shè)定ShadowPath。QuartzCore框架的項(xiàng)目,設(shè)置陰影效果如下:

[myView.layer setShadowOpacity:0.5] ?陰影效果

然而,這種最簡單的添加陰影的方法在性能上卻不是最佳途徑。如果對(duì)這個(gè)添加陰影的View(如果它是一個(gè)UITableViewCell的一部分)做一些動(dòng)畫,您可能會(huì)注意到在動(dòng)畫不是很流暢,有卡頓。這是因?yàn)橛?jì)算陰影需要Core Animation做一個(gè)離屏渲染,以View準(zhǔn)確的形狀確定清楚如何呈現(xiàn)其陰影。解決方法:只要你提前告訴CoreAnimation你要渲染的View的形狀Shape,就會(huì)減少離屏渲染計(jì)算? [myView.layer setShadowPath:[[UIBezierPath? bezierPathWithRect:myView.bounds] CGPath];加上這行代碼,就減少離屏渲染時(shí)間,大大提高了性能。

20. 優(yōu)化的Table View。正確使用reuseldentifier重用可用的cell, 盡量保證views的opaque屬性為YES不透明, 對(duì)于圖片的調(diào)整,在加載前要調(diào)整frame適當(dāng)后再加載, 緩存行高, 如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來自網(wǎng)絡(luò)使用異步加載緩存請(qǐng)求結(jié)果, 盡量控制subViews的數(shù)量,不宜過多, 盡量不適用 cellForRowAtIndexPath: 方法,如果要用到它,就緩存請(qǐng)求的結(jié)果, 使用正確地?cái)?shù)據(jù)結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù), 最好使用屬性 “rowHight”、“sectionFooterHeight”、“sectionHeaderHeight”來設(shè)置高度,最好不適用代理方法來設(shè)置,提高代碼性能。

21. 選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)。iPhone會(huì)為每一個(gè)應(yīng)用程序生成一個(gè)私有目錄,這個(gè)目錄位于/Users/*your user name*/Library/Application Support/iPhone Simulator/5.0/Applications,并隨機(jī)生成一個(gè)數(shù)字+字母的目錄名,在每一次應(yīng)用程序啟動(dòng)時(shí),這個(gè)目錄名都會(huì)隨機(jī)變化。

22. 加速啟動(dòng)時(shí)間(Speed up Launch Time )。減少程序啟動(dòng)過程中的任務(wù), App的啟動(dòng)時(shí)間非常重要,特別是第一次啟動(dòng)的時(shí)候。第一影響意味著太多了!最大的事情是保證你的App開始盡量的快,盡量的多的執(zhí)行異步任務(wù),不如網(wǎng)絡(luò)請(qǐng)求,數(shù)據(jù)庫訪問,或者數(shù)據(jù)解析。

23. 使用Autorelease Pool自動(dòng)釋放池。NSAutoreleasePool負(fù)責(zé)釋放在代碼塊中的自動(dòng)釋放對(duì)象。通常,它是被UIKit自動(dòng)調(diào)用的。但是也有一些場景我們需要手動(dòng)創(chuàng)建NSAutoreleasePools。舉個(gè)例子,如果你創(chuàng)建太多的臨時(shí)對(duì)象在你的代碼中,你會(huì)注意到你的內(nèi)存用量會(huì)增加直到對(duì)象被釋放掉。問題是內(nèi)存只有在UIKit排空(drains)自動(dòng)釋放池的時(shí)候才能被釋放,這意味著內(nèi)存被占用的時(shí)間超過了需要。在每次迭代之后會(huì)自動(dòng)釋放所有的對(duì)象。你可以閱讀更多關(guān)于NSAutoreleasePool的內(nèi)容 Apple’s official documentation。

24. 選擇是否緩存圖片。這里有兩種方法去加載app束中的Image,第一個(gè)常見的方式是用imageNamed.,第二個(gè)是使用imageWithContentsOfFile。為什么會(huì)有兩種方法,它們有效率嗎?imageNamed 在載入時(shí)有緩存的優(yōu)勢,官方文檔 documentation for imageNamed是這樣解釋的:這個(gè)方法看起來在系統(tǒng)緩存一個(gè)圖像對(duì)象并指定名字,如果存在則返回對(duì)象,如果匹配圖像的對(duì)象不在緩存中,這個(gè)方法會(huì)從指定的文件中加載數(shù)據(jù),并緩存它,然后返回結(jié)果對(duì)象。作為替代,imageWithContendsOfFile 簡單的載入圖像并不會(huì)緩存。如果你加載只使用一次大圖片,那就不需要緩存。這種情況imageWithContendsOfFile會(huì)非常好,這種方式不會(huì)浪費(fèi)內(nèi)存來緩存圖片。然而,imageNamed 對(duì)于要重用的圖片來說是更好的選擇,這種方法節(jié)約了經(jīng)常的從磁盤加載圖片的時(shí)間。

25. 盡量避免日期格式轉(zhuǎn)換。如果你要用NSDateFormatter來解析日期數(shù)據(jù),你就得小心對(duì)待了。之前提到過,盡量的重用NSDateFormatters總是一個(gè)好的想法。如果你可以控制你所處理的日期格式,盡量選擇Unix時(shí)間戳。你可以方便地從時(shí)間戳轉(zhuǎn)換到NSDate:

- (NSDate*)dateFromUnixTimestamp:(NSTimeInterval)timestamp { return [NSDate dateWithTimeIntervalSince1970:timestamp]; }。

26.在Xcode7.2中new一個(gè)新的UIView,會(huì)默認(rèn)添加如下代碼:

drawRect方法

從這段話中在此證明,UIView確實(shí)沒有drawRect:的默認(rèn)實(shí)現(xiàn)。而空實(shí)現(xiàn)會(huì)對(duì)性能有負(fù)面影響,網(wǎng)上的說法是,是進(jìn)入這個(gè)方法之前,需要生成繪制上下文,也就是在這個(gè)方法中使用UIGraphicsGetCurrentContext()這個(gè)方法獲取的上下文。而在子類中提供了drawRect:后,堆棧是這樣的

提供了drawRect:后堆棧

顯然,drawLayer:inContext會(huì)調(diào)用drawRect:來繪制,所以在UIView中實(shí)現(xiàn)drawLayer:inContext是不可取的,你很容易忘記調(diào)用drawRect:,即便你記得,你也并不知道drawLayer:inContext又沒做了其他事情,考慮不應(yīng)該drawRect:的空實(shí)現(xiàn),而上下文也會(huì)在drawLayer:inContext:中傳入,我猜測可能蘋果在默認(rèn)的drawLayer:inContext:中先判斷了delegate是否實(shí)現(xiàn)了drawRect:方法,如果是則調(diào)用delegate的方法,否則自己管理這個(gè)繪制的過程,并對(duì)這個(gè)過程做了許多優(yōu)化。總而言之,

在UIView中,永遠(yuǎn)不要override drawLayer:inContext:這個(gè)方法。

最后在iOS上進(jìn)行性能分析的時(shí)候,可以首先考慮借助instruments這個(gè)利器分析出問題出在哪,不要憑空想象,不然你可能把精力花在了1%的問題上,最后發(fā)現(xiàn)其實(shí)啥都沒優(yōu)化,比如要查看程序哪些部分最耗時(shí),可以使用Time Profiler,要查看內(nèi)存是否泄漏了,可以使用Leaks等。關(guān)于instruments網(wǎng)上有很多資料,作為一個(gè)合格iOS開發(fā)者,熟悉這個(gè)工具還是很有必要的。

-----

我是楚簡約,感謝您的閱讀,

喜歡就點(diǎn)個(gè)贊唄,“?喜歡”,

鼓勵(lì)又不花錢,你在看,我就繼續(xù)寫~

非簡書用戶,可以點(diǎn)右上角的三個(gè)“...”,然后"在Safari中打開”,就可以點(diǎn)贊咯~

----


文/楚_簡書書_簡約(簡書作者)。

最后編輯于
?著作權(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),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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