簡單-UIKit性能分析優(yōu)化

本文是基于UIKit性能調(diào)優(yōu)實戰(zhàn)講解 - 簡書這篇文章的總結(jié)詳細請看原文

實例講解可以參考:小心別讓圓角成了你列表的幀數(shù)殺手 - CocoaChina_讓移動開發(fā)更簡單

fps表示frames per second,也就是每秒鐘顯示多少幀畫面。對于靜止不變的內(nèi)容,我們不需要考慮它的刷新率,但在執(zhí)行動畫或滑動時,fps的值直接反映出滑動的流暢程度

調(diào)試優(yōu)化:

1.圖層混合:

首先理解像素:像素就是屏幕上的每一個點由RGB三種顏色構(gòu)成有時候會帶有alpha,如果某一個區(qū)域覆蓋了多個layer,最后的顯示效果就會收到影響,顯色混合會消耗一定的GPU,當把最上層的顏色透明度設(shè)置為100%時GPU就會忽略下面所有的layer節(jié)省不必要的運算

第一個調(diào)試選項"Color Blended Layers"正是用于檢測哪里發(fā)生了圖層混合,并用紅色標記出來。因此我們需要盡可能減少看到的紅色區(qū)域。一旦發(fā)現(xiàn)應(yīng)該想法設(shè)法消除它

解決方案:a.將控件的opaque = true 原理是希望避免圖層混合,但是作用不大,因為UIView的這個屬性默認就是true只要不是人為的設(shè)置為透明都不會出現(xiàn)圖層混合,但對于UIImageView他本身和圖片都不能為透明的,圖片自身的性質(zhì)也可能會對結(jié)果產(chǎn)生影響,比opaque屬性更重要的是backgroundColor屬性,如果不設(shè)置這個屬性,控件依然被認為是透明的,所以我們做的是將他設(shè)置為白色

PS:如果label文字有中文,依然會出現(xiàn)圖層混合,這是因為此時label多了一個sublayer,如果有好的解決辦法歡迎告訴我。

2.光柵化光柵化會導致離屏渲染

光柵化是將一個layer預先渲染成位圖(bitmap),然后加入緩存中。如果對于陰影效果這樣比較消耗資源的靜態(tài)內(nèi)容進行緩存,可以得到一定幅度的性能提升。demo中的這一行代碼表示將label的layer光柵化:

label.layer.shouldRasterize?=?true

Instrument中,第二個調(diào)試選項是“Color Hits Green and Misses Red”,它表示如果命中緩存則顯示為綠色,否則顯示為紅色,顯然綠色越多越好,紅色越少越好。

光柵化的緩存機制是一把雙刃劍,先寫入緩存再讀取有可能消耗較多的時間。因此光柵化僅適用于較復雜的、靜態(tài)的效果。通過Instrument的調(diào)試發(fā)現(xiàn),這里使用光柵化經(jīng)常出現(xiàn)未命中緩存的情況,如果沒有特殊需要則可以關(guān)閉光柵化

3.顏色格式:

像素在內(nèi)存中的布局和它在磁盤中的存儲方式并不相同??紤]一種簡單的情況:每個像素有R、G、B和alpha四個值,每個值占用1字節(jié),因此每個像素占用4字節(jié)的內(nèi)存空間。一張1920*1080的照片(iPhone6 Plus的分辨率)一共有2,073,600個像素,因此占用了超過8Mb的內(nèi)存。但是一張同樣分辨率的PNG格式或JPEG格式的圖片一般情況下不會有這么大。這是因為JPEG將像素數(shù)據(jù)進行了一種非常復雜且可逆的轉(zhuǎn)化。

比如應(yīng)用中有一些從網(wǎng)絡(luò)下載的圖片,而GPU恰好不支持這個格式,這就需要CPU預先進行格式轉(zhuǎn)化

第三個選項“Color Copied Images”就用來檢測這種實時的格式轉(zhuǎn)化,如果有則會將圖片標記為藍色。

4.圖片大小

第五個選項“Color Misaligned Images”。它表示如果圖片需要縮放則標記為黃色,如果沒有像素對齊則標記為紫色

第三個優(yōu)化是調(diào)整所有圖片的像素大小以避免不必要的縮放。

5.離屏渲染

正常的渲染通道:OpenGL提交一個命令到Command Buffer,隨后GPU開始渲染,渲染結(jié)果放到Render Buffer中,這是正常的渲染流程

有一些復雜的效果無法直接渲染出結(jié)果,它需要分步渲染最后再組合起來,比如添加一個蒙版(mask):GPU分別得到了紋理(texture,也就是那個相機圖標)和layer(藍色的蒙版)的渲染結(jié)果。但這兩個渲染結(jié)果沒有直接放入Render Buffer中,也就表示這是離屏渲染。直到第三個渲染通道,才把兩者組合起來放入Render Buffer中。離屏渲染意味著把渲染結(jié)果臨時保存,等用到時再取出,因此相對于普通渲染更占用資源。

第六個選項“Color Offscreen-Rendered Yellow”會把需要離屏渲染的地方標記為黃色,大部分情況下我們需要盡可能避免黃色的出現(xiàn)。離屏渲染可能會自動觸發(fā),也可以手動觸發(fā)。以下情況可能會導致觸發(fā)離屏渲染:

重寫drawRect方法

有mask或者是陰影(layer.masksToBounds, layer.shadow*),模糊效果也是一種mask

layer.shouldRasterize = true

前兩者會自動觸發(fā)離屏渲染,第三種方法是手動開啟離屏渲染。

第四個優(yōu)化,在設(shè)置陰影效果的四行代碼下面添加一行:

imgView.layer.shadowPath = UIBezierPath(rect: imgView.bounds).CGPath

這行代碼制定了陰影路徑,如果沒有手動指定,Core Animation會去自動計算,這就會觸發(fā)離屏渲染。如果人為指定了陰影路徑,就可以免去計算,從而避免產(chǎn)生離屏渲染。

設(shè)置cornerRadius本身并不會導致離屏渲染,但很多時候它還需要配合layer.masksToBounds = true使用。根據(jù)之前的總結(jié),設(shè)置masksToBounds會導致離屏渲染。解決方案是盡可能在滑動時避免設(shè)置圓角,如果必須設(shè)置圓角,可以使用光柵化技術(shù)將圓角緩存起來:

//?設(shè)置圓角 ? ?ps:用后用后無效果

label.layer.masksToBounds?=?true

label.layer.cornerRadius?=?8

label.layer.shouldRasterize?=?true

label.layer.rasterizationScale?=?layer.contentsScale

6.快速路徑:

之前將離屏渲染和渲染路徑時的示意圖么,離屏渲染的最后一步是把此前的多個路徑組合起來。如果這個組合過程能由CPU完成,就會大量減少GPU的工作。這種技術(shù)在繪制地圖中可能用到。

第七個選項“Color Compositing Fast-Path Blue”用于標記由硬件繪制的路徑,藍色越多越好。

7.變化區(qū)域

刷新視圖時,我們應(yīng)該把需要重繪的區(qū)域盡可能縮小。對于未發(fā)生變化的內(nèi)容則不應(yīng)該重繪,第八個選項“Flash updated Regions”用于標記發(fā)生重繪的區(qū)域。一個典型的例子是系統(tǒng)的時鐘應(yīng)用,絕大多數(shù)時候只有顯示秒針的區(qū)域需要重繪:

總結(jié)

如果你一步一步做到了這里,我想一定會有不少收益。不過,學而不思則罔,思而不學則殆。動手實踐后還是應(yīng)該總結(jié)提煉,優(yōu)化滑動性能主要涉及三個方面:

避免圖層混合

確保控件的opaque屬性設(shè)置為true,確保backgroundColor和父視圖顏色一致且不透明

如無特殊需要,不要設(shè)置低于1的alpha值

確保UIImage沒有alpha通道

避免臨時轉(zhuǎn)換

確保圖片大小和frame一致,不要在滑動時縮放圖片

確保圖片顏色格式被GPU支持,避免勞煩CPU轉(zhuǎn)換

慎用離屏渲染

絕大多數(shù)時候離屏渲染會影響性能

重寫drawRect方法,設(shè)置圓角、陰影、模糊效果,光柵化都會導致離屏渲染

設(shè)置陰影效果是加上陰影路徑

滑動時若需要圓角效果,開啟光柵化

實戰(zhàn)

本文的demo可以在我的Github上下載,然后一步一步自己體驗優(yōu)化過程。但demo畢竟是刻意搭建的一個環(huán)境,我會在我自己的仿寫的簡書app上不斷進行實戰(zhàn)優(yōu)化,歡迎共同學習交流。

代碼:

UIView圓角:

#import "UIView+AddCorner.h"

@implementation UIView (AddCorner)

-(double)roundbyuint:(double) num anUint:(double )unit{

double remain = modf(num, &unit);

if (remain > unit / 2.0) {

return [self ceilbyuint:num andUint:unit];

} else {

return [self floorbyuint:num andUint:unit];

}

}

-(double)ceilbyuint:(double)num andUint:(double)unit{

return num - modf(num, &unit) + unit;

//將浮點數(shù)num分解成整數(shù)部分和小數(shù)部分

}

-(double)floorbyuint:(double)num andUint:(double)unit{

return num - modf(num, &unit) ;

}

-(double)piexl:(double)num{

double unit;

switch ((int)[UIScreen mainScreen].scale) {

case 1: unit = 1.0/1.0; break;

case 2: unit = 1.0/2.0; break;

case 3: unit = 1.0/3.0; break;

default: unit = 0.0;

break;

}

return [self roundbyuint:num anUint:unit];

}

-(void)kt_viewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor{

UIImageView * imageView = [[UIImageView alloc]initWithImage:[self ViewAddCorner:radius andBorderWidth:borderWidth andBorderColor:borderColor andBackgroundColor:backgroundColor]] ;

[self insertSubview:imageView atIndex:0];

}

-(UIImage *)ViewAddCorner:(CGFloat)radius andBorderWidth:(CGFloat)borderWidth andBorderColor:(UIColor*)borderColor andBackgroundColor:(UIColor*)backgroundColor {

CGSize sizeToFit = CGSizeMake((double)CGRectGetWidth(self.frame), (double)CGRectGetHeight(self.frame));

CGFloat halfBorderWidth = (CGFloat)borderWidth / 2.0;

UIGraphicsBeginImageContextWithOptions(sizeToFit, false, [UIScreen mainScreen].scale);

CGContextRef context = UIGraphicsGetCurrentContext();

CGContextSetLineWidth(context, borderWidth);

CGContextSetStrokeColorWithColor(context, borderColor.CGColor);

CGContextSetFillColorWithColor(context, backgroundColor.CGColor);

CGFloat width = sizeToFit.width, height = sizeToFit.height;

CGContextMoveToPoint(context, width - halfBorderWidth, radius + halfBorderWidth);? // 開始坐標右邊開始

CGContextAddArcToPoint(context, width - halfBorderWidth, height - halfBorderWidth, width - radius - halfBorderWidth, height - halfBorderWidth, radius);? // 右下角角度

CGContextAddArcToPoint(context, halfBorderWidth, height - halfBorderWidth, halfBorderWidth, height - radius - halfBorderWidth, radius); // 左下角角度

CGContextAddArcToPoint(context, halfBorderWidth, halfBorderWidth, width - halfBorderWidth, halfBorderWidth, radius); // 左上角

CGContextAddArcToPoint(context, width - halfBorderWidth, halfBorderWidth, width - halfBorderWidth, radius + halfBorderWidth, radius) ;// 右上角

CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);

UIImage? *output = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return output;

}

@end

UIImageView圓角:

#import "UIImageView+AddCorner.h"

@implementation UIImageView (AddCorner)

-(void)kt_ImageViewAddCornerWithRadius:(CGFloat)radius{

// self.image = self.image?:[self.image imageAddCornerWithRadius:radius andSize:self.bounds.size];

//注意第三個選項的設(shè)置

UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, [UIScreen mainScreen].scale);

//在繪制之前先裁剪出一個圓形

[[UIBezierPath bezierPathWithRoundedRect:self.bounds

cornerRadius:radius] addClip];

//圖片在設(shè)置的圓形里面進行繪制

[self.image drawInRect:self.bounds];

//獲取圖片

self.image = UIGraphicsGetImageFromCurrentImageContext();

//結(jié)束繪制

UIGraphicsEndImageContext();

}

@end

UIImage圓角:

#import "UIImage+ImageRoundedCorner.h"

@implementation UIImage (ImageRoundedCorner)

- (UIImage*)imageAddCornerWithRadius:(CGFloat)radius andSize:(CGSize)size{

CGRect rect = CGRectMake(0, 0, size.width, size.height);

UIGraphicsBeginImageContextWithOptions(size, NO, [UIScreen mainScreen].scale);

CGContextRef ctx = UIGraphicsGetCurrentContext();

UIBezierPath * path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];

CGContextAddPath(ctx,path.CGPath);

CGContextClip(ctx);

[self drawInRect:rect];

CGContextDrawPath(ctx, kCGPathFillStroke);

UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();

UIGraphicsEndImageContext();

return newImage;

}

@end

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,908評論 6 541
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,324評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,018評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,675評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,417評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,783評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,779評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,960評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,522評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,267評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,471評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,009評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,698評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,099評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,386評論 1 294
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,204評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,436評論 2 378

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

  • 實際開發(fā)中,大多會遇到圓角或者圓形的控件的情況。通常,簡便的解決方案主要是: 1.讓美工做一個圓角的圖片...
    LanWor閱讀 1,668評論 1 5
  • Quartz2D以及drawRect的重繪機制字數(shù)1487 閱讀21 評論1 喜歡1一、什么是Quartz2D Q...
    PurpleWind閱讀 786評論 0 3
  • 圓角是一種常見UI效果設(shè)計,與直角相比要柔和許多,適合暖色調(diào)設(shè)計。而在設(shè)置圓角時會帶來一定的性能損耗,下面介紹幾種...
    簡魚7819閱讀 2,689評論 0 10
  • //設(shè)置尺寸為屏幕尺寸的時候self.window = [[UIWindow alloc] initWithFra...
    LuckTime閱讀 831評論 0 0
  • 做前端的小伙伴無論在學習還是工作中,應(yīng)該還是或多或少會遇到點IE兼容問題,當然我們巴不得IE的份額越來越低..( ̄...
    Aleph_Zheng閱讀 170評論 0 0