UITableView的復(fù)用機(jī)制
UITableView首先加載一屏幕(假設(shè)UITableView的大小是整個(gè)屏幕的大小)所需要的UITableViewCell,具體個(gè)數(shù)要根據(jù)每個(gè)cell的高度而定,總之肯定要鋪滿整個(gè)屏幕,更準(zhǔn)確說(shuō)當(dāng)前加載的cell的高度要大于屏幕高度蛮位。然后你往上滑動(dòng)垛孔,想要查看更多的內(nèi)容,那么肯定需要一個(gè)新的cell放在已經(jīng)存在內(nèi)容的下邊譬正。這時(shí)候先不去生成簇捍,而是先去UITableView自己的一個(gè)資源池里去獲取划滋。這個(gè)資源池里放了已經(jīng)生成的而且能用的cell。如果資源池是空的話才會(huì)主動(dòng)生成一個(gè)新的cell腰湾。那么這個(gè)資源池里的cell又來(lái)自哪里呢雷恃?當(dāng)你滑動(dòng)時(shí)視圖是,位于最頂部的cell會(huì)相應(yīng)的往上滑動(dòng)费坊,直到它徹底消失在屏幕上倒槐,消失的cell去了哪里呢?你肯定想到了附井,是的讨越,它被UITableView放到資源池里了两残。其他cell也是這樣,只要一滑出屏幕就放入資源池把跨。這樣人弓,有進(jìn)有出,總共需要大約一屏幕多一兩個(gè)的cell就夠了着逐。相對(duì)于1000來(lái)說(shuō)節(jié)省的資源就是指數(shù)級(jí)啊崔赌,完美解決了性能問(wèn)題。
常用的代碼
//方法1
- (void)viewDidLoad {
[super viewDidLoad];
// Setup table view.
self.myTableView.delegate = self;
self.myTableView.dataSource = self;
[self.myTableView registerClass:[MyTableViewCell class] forCellReuseIdentifier:@"MyTableViewCell"];
//或者[UITableView registerNib:forCellReuseIdentifier:]方法
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"MyTableViewCell";
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
//do something
return cell;
}
//方法2
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"UITableViewCell";
UITableViewCell *cell = nil;
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
//不要在這里設(shè)置cell的屬性
}
//do something
return cell;
}
注意這里- [UITableView registerClass:forCellReuseIdentifier:]
和- [UITableView registerNib:forCellReuseIdentifier:]
這兩個(gè)方法一定不能用錯(cuò)耸别,否則就會(huì)報(bào)錯(cuò)健芭。
還有就是我注釋中說(shuō)的不要在if里面設(shè)置cell的屬性,這就是因?yàn)樗膹?fù)用機(jī)制秀姐,如果在那里設(shè)置了慈迈,你后面的cell因?yàn)槭菑?fù)用的前面的cell,所以不會(huì)執(zhí)行if里面的代碼囊扳,就會(huì)導(dǎo)致你設(shè)置的屬性失效吩翻。
UITableView的優(yōu)化
1兜看、cell高度的計(jì)算
如果是固定高度锥咸,則直接設(shè)置self.tableView.rowHeight = 88;
,不要重寫-(CGFloat)tableView:(UITableView *)tableViewheightForRowAtIndexPath:(NSIndexPath *)indexPath
這個(gè)方法细移,重寫了這個(gè)方法后前面設(shè)置的rowHeight將會(huì)失效搏予,并且每次顯示一個(gè)cell都會(huì)調(diào)用一次這個(gè)方法,所以重寫這個(gè)方法肯定沒(méi)有直接設(shè)置rowHeight效率高弧轧。
iOS8之后有了self-sizing cell的概念雪侥,cell可以自己算出高度,使用self-sizing cell需要滿足以下三個(gè)條件:
- 使用Autolayout進(jìn)行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)精绎。
- 指定TableView的estimatedRowHeight屬性的默認(rèn)值速缨,就是初始化的一個(gè)默認(rèn)高度。
- 指定TableView的rowHeight屬性為UITableViewAutomaticDimension代乃。
- (void)viewDidload {
self.myTableView.estimatedRowHeight = 44.0;
self.myTableView.rowHeight = UITableViewAutomaticDimension;
}
除了提高cell高度的計(jì)算效率之外旬牲,對(duì)于已經(jīng)計(jì)算出的高度,也可以進(jìn)行緩存搁吓,對(duì)于已經(jīng)計(jì)算過(guò)的高度原茅,沒(méi)有必要進(jìn)行計(jì)算第二次。
2堕仔、渲染
GPU渲染機(jī)制:
CPU 計(jì)算好顯示內(nèi)容提交到 GPU擂橘,GPU 渲染完成后將渲染結(jié)果放入幀緩沖區(qū),隨后視頻控制器會(huì)按照 VSync 信號(hào)逐行讀取幀緩沖區(qū)的數(shù)據(jù)摩骨,經(jīng)過(guò)可能的數(shù)模轉(zhuǎn)換傳遞給顯示器顯示通贞。
GPU屏幕渲染有以下兩種方式:
- On-Screen Rendering
意為當(dāng)前屏幕渲染朗若,指的是GPU的渲染操作是在當(dāng)前用于顯示的屏幕緩沖區(qū)中進(jìn)行。 - Off-Screen Rendering
意為離屏渲染昌罩,指的是GPU在當(dāng)前屏幕緩沖區(qū)以外新開(kāi)辟一個(gè)緩沖區(qū)進(jìn)行渲染操作捡偏。
離屏渲染的代價(jià)
相比于當(dāng)前屏幕渲染,離屏渲染的代價(jià)是很高的峡迷,主要體現(xiàn)在兩個(gè)方面:
- 創(chuàng)建新緩沖區(qū)
要想進(jìn)行離屏渲染银伟,首先要?jiǎng)?chuàng)建一個(gè)新的緩沖區(qū)。
- 上下文切換
離屏渲染的整個(gè)過(guò)程绘搞,需要多次切換上下文環(huán)境:先是從當(dāng)前屏幕(On-Screen)切換到離屏(Off-Screen)彤避;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕夯辖。而上下文環(huán)境的切換是要付出很大代價(jià)的琉预。
總之:離屏渲染會(huì)付出很大的開(kāi)銷,能避免離屏渲染盡量就不要離屏渲染
下面的情況或操作會(huì)引發(fā)離屏渲染:
- 設(shè)置透明(alpha)屬性
- 為圖層設(shè)置遮罩(layer.mask)
- 將圖層的layer.masksToBounds / view.clipsToBounds屬性設(shè)置為true
- 將圖層layer.allowsGroupOpacity屬性設(shè)置為YES和layer.opacity小于1.0
- 為圖層設(shè)置陰影(layer.shadow *)蒿褂。
- 為圖層設(shè)置layer.shouldRasterize=true
- 具有l(wèi)ayer.cornerRadius圆米,layer.edgeAntialiasingMask,layer.allowsEdgeAntialiasing的圖層
- 文本(任何種類啄栓,包括UILabel娄帖,CATextLayer,Core Text等)使用CGContext在drawRect :方法中繪制大部分情況下會(huì)導(dǎo)致離屏渲染昙楚,甚至僅僅是一個(gè)空的實(shí)現(xiàn)
iOS 9.0 之前UIimageView跟UIButton設(shè)置圓角都會(huì)觸發(fā)離屏渲染近速。
iOS 9.0 之后UIButton設(shè)置圓角會(huì)觸發(fā)離屏渲染,而UIImageView里png圖片設(shè)置圓角不會(huì)觸發(fā)離屏渲染了堪旧,如果設(shè)置其他陰影效果之類的還是會(huì)觸發(fā)離屏渲染的削葱。
常用的幾個(gè)優(yōu)化
1、圓角優(yōu)化
在APP開(kāi)發(fā)中淳梦,圓角圖片還是經(jīng)常出現(xiàn)的析砸。如果一個(gè)界面中只有少量圓角圖片或許對(duì)性能沒(méi)有非常大的影響,但是當(dāng)圓角圖片比較多的時(shí)候就會(huì)APP性能產(chǎn)生明顯的影響爆袍。
我們?cè)O(shè)置圓角一般通過(guò)如下方式:
imageView.layer.cornerRadius=CGFloat(10);
imageView.layer.masksToBounds=YES;
優(yōu)化方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個(gè)圓角
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//開(kāi)始對(duì)imageView進(jìn)行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用貝塞爾曲線畫出一個(gè)圓形圖
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width/2.0] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束畫圖
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
優(yōu)化方案2:使用CAShapeLayer和UIBezierPath設(shè)置圓角
UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image=[UIImage imageNamed:@"myImg"];
UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
//設(shè)置大小
maskLayer.frame=imageView.bounds;
//設(shè)置圖形樣子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.view addSubview:imageView];
對(duì)于方案2的解釋:
- 使用CAShapeLayer(屬于CoreAnimation)與貝塞爾曲線可以實(shí)現(xiàn)不在view的drawRect(繼承于CoreGraphics走的是CPU,消耗的性能較大)方法中畫出一些想要的圖形
- CAShapeLayer動(dòng)畫渲染直接提交到手機(jī)的GPU當(dāng)中首繁,相較于view的drawRect方法使用CPU渲染而言,其效率極高螃宙,能大大優(yōu)化內(nèi)存使用情況蛮瞄。
總的來(lái)說(shuō)就是用CAShapeLayer的內(nèi)存消耗少,渲染速度快谆扎,建議使用優(yōu)化方案2挂捅。
2、shadow優(yōu)化
對(duì)于shadow堂湖,如果圖層是個(gè)簡(jiǎn)單的幾何圖形或者圓角圖形闲先,我們可以通過(guò)設(shè)置shadowPath來(lái)優(yōu)化性能状土,能大幅提高性能。示例如下:
imageView.layer.shadowColor=[UIColor grayColor].CGColor;
imageView.layer.shadowOpacity=1.0;
imageView.layer.shadowRadius=2.0;
UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.bounds];
imageView.layer.shadowPath=path.CGPath;
我們還可以通過(guò)設(shè)置shouldRasterize屬性值為YES來(lái)強(qiáng)制開(kāi)啟離屏渲染伺糠。其實(shí)就是光柵化(Rasterization)蒙谓。既然離屏渲染這么不好,為什么我們還要強(qiáng)制開(kāi)啟呢训桶?當(dāng)一個(gè)圖像混合了多個(gè)圖層累驮,每次移動(dòng)時(shí),每一幀都要重新合成這些圖層舵揭,十分消耗性能谤专。當(dāng)我們開(kāi)啟光柵化后,會(huì)在首次渲染的時(shí)候產(chǎn)生一個(gè)位圖緩存午绳,當(dāng)再次使用時(shí)候就會(huì)復(fù)用這個(gè)緩存置侍。但是如果圖層發(fā)生改變的時(shí)候就會(huì)重新產(chǎn)生位圖緩存。所以這個(gè)功能一般不能用于UITableViewCell中拦焚,cell的復(fù)用反而降低了性能蜡坊。最好用于圖層較多的靜態(tài)內(nèi)容的圖形。而且產(chǎn)生的位圖緩存的大小是有限制的赎败,一般是2.5個(gè)屏幕尺寸秕衙。在100ms之內(nèi)不使用這個(gè)緩存,緩存也會(huì)被刪除螟够。所以我們要根據(jù)使用場(chǎng)景而定灾梦。
3峡钓、其他的一些優(yōu)化建議
- 當(dāng)我們需要圓角效果時(shí)妓笙,可以使用一張中間透明圖片蒙上去
- 使用ShadowPath指定layer陰影效果路徑
- 使用異步進(jìn)行l(wèi)ayer渲染(Facebook開(kāi)源的異步繪制框架AsyncDisplayKit)
- 設(shè)置layer的opaque值為YES,減少?gòu)?fù)雜圖層合成(如果opaque設(shè)置NO能岩,那么Alpha應(yīng)該小于1)
- 盡量使用不包含透明(alpha)通道的圖片資源
- 盡量設(shè)置layer的大小值為整形值
- 直接讓美工把圖片切成圓角進(jìn)行顯示寞宫,這是效率最高的一種方案
- 很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
- 使用代碼手動(dòng)生成圓角Image設(shè)置到要顯示的View上拉鹃,利用UIBezierPath(CoreGraphics框架)畫出來(lái)圓角圖片
3辈赋、其它
- 1) 減少視圖的數(shù)目:
我們?cè)赾ell上添加系統(tǒng)控件的時(shí)候,實(shí)際上系統(tǒng)都會(huì)調(diào)用底層的接口進(jìn)行繪制膏燕,大量添加控件時(shí)钥屈,會(huì)消耗很大的資源并且也會(huì)影響渲染的性能。當(dāng)使用默認(rèn)的UITableViewCell并且在它的ContentView上面添加控件時(shí)會(huì)相當(dāng)消耗性能坝辫。所以目前最佳的方法還是繼承UITableViewCell篷就,并重寫drawRect方法,并且這里的繪制過(guò)程可以通過(guò)多線程異步繪制近忙。 - 2)減少多余的繪制操作:
在實(shí)現(xiàn)drawRect方法的時(shí)候竭业,它的參數(shù)rect就是我們需要繪制的區(qū)域智润,在rect范圍之外的區(qū)域我們不需要進(jìn)行繪制,否則會(huì)消耗相當(dāng)大的資源未辆。 - 3)不要給cell動(dòng)態(tài)添加subView:
在初始化cell的時(shí)候就將所有需要展示的添加完畢窟绷,然后根據(jù)需要來(lái)設(shè)置hide屬性顯示和隱藏。 - 4)滑動(dòng)時(shí)按需加載對(duì)應(yīng)的內(nèi)容:
滑動(dòng)很快時(shí)咐柜,只加載目標(biāo)范圍內(nèi)的cell兼蜈,這樣按需加載(配合SDWebImage),極大提高流暢度拙友,但是這樣在滑動(dòng)過(guò)程中就會(huì)暫時(shí)顯示空白饭尝,這就需要在性能和用戶體驗(yàn)中權(quán)衡了。
最后安利一篇YY大神的博客
YY大神:iOS 保持界面流暢的技巧.