轉(zhuǎn)自:http://blog.ibireme.com/2015/11/12/smooth_user_interfaces_for_ios/
對象創(chuàng)建
對象的創(chuàng)建會分配內(nèi)存匪补、調(diào)整屬性虹统、甚至還有讀取文件等操作,比較消耗 CPU 資源参滴。盡量用輕量的對象代替重量的對象,可以對性能有所優(yōu)化锻弓。比如 CALayer 比 UIView 要輕量許多砾赔,那么不需要響應(yīng)觸摸事件的控件,用 CALayer 顯示會更加合適青灼。如果對象不涉及 UI 操作暴心,則盡量放到后臺線程去創(chuàng)建,但可惜的是包含有 CALayer 的控件杂拨,都只能在主線程創(chuàng)建和操作专普。通過 Storyboard 創(chuàng)建視圖對象時,其資源消耗會比直接通過代碼創(chuàng)建對象要大非常多弹沽,在性能敏感的界面里檀夹,Storyboard 并不是一個好的技術(shù)選擇。
盡量推遲對象創(chuàng)建的時間策橘,并把對象的創(chuàng)建分散到多個任務(wù)中去炸渡。盡管這實現(xiàn)起來比較麻煩,并且?guī)淼膬?yōu)勢并不多丽已,但如果有能力做蚌堵,還是要盡量嘗試一下。如果對象可以復(fù)用,并且復(fù)用的代價比釋放吼畏、創(chuàng)建新對象要小督赤,那么這類對象應(yīng)當(dāng)盡量放到一個緩存池里復(fù)用。
對象調(diào)整
對象的調(diào)整也經(jīng)常是消耗 CPU 資源的地方宫仗。這里特別說一下 CALayer:CALayer 內(nèi)部并沒有屬性够挂,當(dāng)調(diào)用屬性方法時,它內(nèi)部是通過運行時 resolveInstanceMethod 為對象臨時添加一個方法藕夫,并把對應(yīng)屬性值保存到內(nèi)部的一個 Dictionary 里孽糖,同時還會通知 delegate、創(chuàng)建動畫等等毅贮,非常消耗資源办悟。UIView 的關(guān)于顯示相關(guān)的屬性(比如 frame/bounds/transform)等實際上都是 CALayer 屬性映射來的,所以對 UIView 的這些屬性進行調(diào)整時滩褥,消耗的資源要遠大于一般的屬性病蛉。對此你在應(yīng)用中,應(yīng)該盡量減少不必要的屬性修改瑰煎。
當(dāng)視圖層次調(diào)整時铺然,UIView、CALayer 之間會出現(xiàn)很多方法調(diào)用與通知酒甸,所以在優(yōu)化性能時魄健,應(yīng)該盡量避免調(diào)整視圖層次、添加和移除視圖插勤。
對象銷毀
對象的銷毀雖然消耗資源不多沽瘦,但累積起來也是不容忽視的。通常當(dāng)容器類持有大量對象時农尖,其銷毀時的資源消耗就非常明顯析恋。同樣的,如果對象可以放到后臺線程去釋放盛卡,那就挪到后臺線程去助隧。這里有個小 Tip:把對象捕獲到 block 中,然后扔到后臺隊列去隨便發(fā)送個消息以避免編譯器警告滑沧,就可以讓對象在后臺線程銷毀了喇颁。
- 測試用的代碼(同步釋放時耗時)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSMutableArray *t_list = [[NSMutableArray alloc] init];
for (int i = 0; i < 9999999 * 3; i++) {
TestObject *ob = [[TestObject alloc] init];
[t_list addObject:ob];
}
NSDate *s_date = [NSDate date];
t_list = nil;
NSDate *e_date = [NSDate date];
NSLog(@"相差的時間是:%d秒",(int)[e_date timeIntervalSinceDate:s_date]);
});
- 修改后的代碼(異步釋放時耗時)
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
NSMutableArray *t_list = [[NSMutableArray alloc] init];
for (int i = 0; i < 9999999 * 3; i++) {
TestObject *ob = [[TestObject alloc] init];
[t_list addObject:ob];
}
NSMutableArray *m_list = t_list;
NSDate *s_date = [NSDate date];
t_list = nil;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[m_list class];
});
NSDate *e_date = [NSDate date];
NSLog(@"相差的時間是:%d秒",(int)[e_date timeIntervalSinceDate:s_date]);
});
布局計算
視圖布局的計算是 App 中最為常見的消耗 CPU 資源的地方。如果能在后臺線程提前計算好視圖布局嚎货、并且對視圖布局進行緩存,那么這個地方基本就不會產(chǎn)生性能問題了蔫浆。
不論通過何種技術(shù)對視圖進行布局殖属,其最終都會落到對 UIView.frame/bounds/center 等屬性的調(diào)整上。上面也說過瓦盛,對這些屬性的調(diào)整非常消耗資源洗显,所以盡量提前計算好布局外潜,在需要時一次性調(diào)整好對應(yīng)屬性,而不要多次挠唆、頻繁的計算和調(diào)整這些屬性处窥。
Autolayout
Autolayout 是蘋果本身提倡的技術(shù),在大部分情況下也能很好的提升開發(fā)效率玄组,但是 Autolayout 對于復(fù)雜視圖來說常常會產(chǎn)生嚴重的性能問題滔驾。隨著視圖數(shù)量的增長,Autolayout 帶來的 CPU 消耗會呈指數(shù)級上升俄讹。具體數(shù)據(jù)可以看這個文章:http://pilky.me/36/哆致。 如果你不想手動調(diào)整 frame 等屬性,你可以用一些工具方法替代(比如常見的 left/right/top/bottom/width/height 快捷屬性)患膛,或者使用 ComponentKit摊阀、AsyncDisplayKit 等框架。
文本計算
如果一個界面中包含大量文本(比如微博微信朋友圈等)踪蹬,文本的寬高計算會占用很大一部分資源胞此,并且不可避免。如果你對文本顯示沒有特殊要求跃捣,可以參考下 UILabel 內(nèi)部的實現(xiàn)方式:用 [NSAttributedString boundingRectWithSize:options:context:] 來計算文本寬高漱牵,用 -[NSAttributedString drawWithRect:options:context:] 來繪制文本。盡管這兩個方法性能不錯枝缔,但仍舊需要放到后臺線程進行以避免阻塞主線程布疙。
如果你用 CoreText 繪制文本,那就可以先生成 CoreText 排版對象愿卸,然后自己計算了灵临,并且 CoreText 對象還能保留以供稍后繪制使用。
文本渲染
屏幕上能看到的所有文本內(nèi)容控件趴荸,包括 UIWebView儒溉,在底層都是通過 CoreText 排版、繪制為 Bitmap 顯示的发钝。常見的文本控件 (UILabel顿涣、UITextView 等),其排版和繪制都是在主線程進行的酝豪,當(dāng)顯示大量文本時涛碑,CPU 的壓力會非常大。對此解決方案只有一個孵淘,那就是自定義文本控件蒲障,用 TextKit 或最底層的 CoreText 對文本異步繪制。盡管這實現(xiàn)起來非常麻煩,但其帶來的優(yōu)勢也非常大揉阎,CoreText 對象創(chuàng)建好后庄撮,能直接獲取文本的寬高等信息,避免了多次計算(調(diào)整 UILabel 大小時算一遍毙籽、UILabel 繪制時內(nèi)部再算一遍)洞斯;CoreText 對象占用內(nèi)存較少,可以緩存下來以備稍后多次渲染坑赡。
圖片的解碼
當(dāng)你用 UIImage 或 CGImageSource 的那幾個方法創(chuàng)建圖片時烙如,圖片數(shù)據(jù)并不會立刻解碼。圖片設(shè)置到 UIImageView 或者 CALayer.contents 中去垮衷,并且 CALayer 被提交到 GPU 前厅翔,CGImage 中的數(shù)據(jù)才會得到解碼。這一步是發(fā)生在主線程的搀突,并且不可避免刀闷。如果想要繞開這個機制,常見的做法是在后臺線程先把圖片繪制到 CGBitmapContext 中仰迁,然后從 Bitmap 直接創(chuàng)建圖片甸昏。目前常見的網(wǎng)絡(luò)圖片庫都自帶這個功能。
圖像的繪制
圖像的繪制通常是指用那些以 CG 開頭的方法把圖像繪制到畫布中徐许,然后從畫布創(chuàng)建圖片并顯示這樣一個過程施蜜。這個最常見的地方就是 [UIView drawRect:] 里面了。由于 CoreGraphic 方法通常都是線程安全的雌隅,所以圖像的繪制可以很容易的放到后臺線程進行翻默。一個簡單異步繪制的過程大致如下(實際情況會比這個復(fù)雜得多,但原理基本一致)
- (void)display {
dispatch_async(backgroundQueue, ^{
CGContextRef ctx = CGBitmapContextCreate(...);
// draw in context...
CGImageRef img = CGBitmapContextCreateImage(ctx);
CFRelease(ctx);
dispatch_async(mainQueue, ^{
layer.contents = img;
});
});
}