iOS App性能優化技巧

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

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

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

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

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

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

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

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

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

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

答案是2個。即我們在numberOfSectionsInCollectionView:方法中返回2即可。

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

9. 重用和延遲加載Views。如果我們在啟動程序時,就把所有的views新建出來,等到用到時就加載。這樣的話,毫不疑問就會在內存存放著許多的views,這樣明顯不行。系統的view默認都是懶加載過程,只有用到view的時候,才會新建加載,節省CPU的消耗,在我們寫程序時也一定參考這種方法。

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

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

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

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

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

15. 避免反復處理數據。小量數據處理:NSKeyedArchiver/NSUserDefaults/Write寫入方式。大量數據SQLite/CoreData?;镜膒list適合Objective-C中內置的數據類,要想存儲和讀取自定義的對象,需要使用歸檔(archive)和反歸檔(unarchiver). 嵌入式數據庫SQLite在處理大型數據時優勢明顯。在不同的場合使用適當的方法,是開發程序時的原則。

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

17. 正確地設置BackgroundImages。在如下類進行設置:UIBarButtonItem/UIButton/UINavigationBar/UISearchBar/UISegmentedControl/UITabBar/UIStepper/UIToolbar。

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

19. 設定ShadowPath。QuartzCore框架的項目,設置陰影效果如下:

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

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

20. 優化的Table View。正確使用reuseldentifier重用可用的cell, 盡量保證views的opaque屬性為YES不透明, 對于圖片的調整,在加載前要調整frame適當后再加載, 緩存行高, 如果cell內現實的內容來自網絡使用異步加載緩存請求結果, 盡量控制subViews的數量,不宜過多, 盡量不適用 cellForRowAtIndexPath: 方法,如果要用到它,就緩存請求的結果, 使用正確地數據結構來存儲數據, 最好使用屬性 “rowHight”、“sectionFooterHeight”、“sectionHeaderHeight”來設置高度,最好不適用代理方法來設置,提高代碼性能。

21. 選擇正確的數據存儲選項。iPhone會為每一個應用程序生成一個私有目錄,這個目錄位于/Users/*your user name*/Library/Application Support/iPhone Simulator/5.0/Applications,并隨機生成一個數字+字母的目錄名,在每一次應用程序啟動時,這個目錄名都會隨機變化。

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

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

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

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

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

26.在Xcode7.2中new一個新的UIView,會默認添加如下代碼:

drawRect方法

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

提供了drawRect:后堆棧

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

在UIView中,永遠不要override drawLayer:inContext:這個方法。

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

-----

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

喜歡就點個贊唄,“?喜歡”,

鼓勵又不花錢,你在看,我就繼續寫~

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

----


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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容