iOS性能優(yōu)化』--UITableView
優(yōu)化方案
一隙赁、善用重用標(biāo)識(shí)
二、設(shè)置預(yù)估行高,預(yù)先緩存動(dòng)態(tài)行高
三、減少SubViews
層級(jí)、異步繪制宣蔚、避免離屏渲染、使用Hidden
隱藏圖層
四认境、分屏加載數(shù)據(jù),預(yù)先異步請(qǐng)求數(shù)據(jù)
五挟鸠、滑動(dòng)TableView
時(shí)叉信,按需加載內(nèi)容
六、cell
類中應(yīng)該避免請(qǐng)求網(wǎng)絡(luò)加載數(shù)據(jù)
七艘希、在willDisplayCell:forRowAtIndexPath:
代理方法中的綁定數(shù)據(jù)
一硼身、善用重用標(biāo)識(shí)
這個(gè)屬于基礎(chǔ)知識(shí)范疇硅急,就不再過(guò)度的講解了。
只需要了解使用static
修飾重用標(biāo)識(shí)名稱能夠保證這個(gè)標(biāo)識(shí)只會(huì)創(chuàng)建一次佳遂,提高性能营袜。接著就是調(diào)用dequeueReusableCellWithIdentifier:
方法獲取緩存池中的Cell
。如果沒(méi)有就調(diào)用initWithStyle:ReusIdentifier:
方法創(chuàng)建一個(gè)新的Cell
丑罪。注意事先需要調(diào)用registerNib/registerClass
方法為TableView
注冊(cè)一下標(biāo)識(shí)荚板。
二、設(shè)置預(yù)估行高吩屹,預(yù)先緩存動(dòng)態(tài)行高
1. 設(shè)置預(yù)估行高
我們知道UITableView
是通過(guò)UITableView
代理方法heightForRowAtIndexPath:
方法來(lái)設(shè)置行高跪另。自從iOS8.0
之后,蘋果新增了self-sizing cell
的概念煤搜,也是cell
可以自己計(jì)算行高免绿,使用需要滿足是三個(gè)條件:
(1) 使用
Autolayout
進(jìn)行UI布局約束
(2) 指定TableView
的estimatedRowHeight
屬性的默認(rèn)值
(3) 指定TableView
的rowHeight
的屬性為UITableViewAutomaticDimension
。
TableView在加載數(shù)據(jù)時(shí)會(huì)先通過(guò)estimatedRowHeight:AtIndexPath
處理全部數(shù)據(jù)擦盾,此時(shí)我們只需要提供一個(gè)粗略的高度嘲驾,待到cell
對(duì)象創(chuàng)建之后再去設(shè)置cell
的真實(shí)高度。而且只會(huì)處理當(dāng)前屏幕范圍內(nèi)的cell
迹卢,這樣子會(huì)顯著的提升加載的性能辽故。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 50.0;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 30.0;
}
2.預(yù)先計(jì)算并緩存行高
自從iOS8.0
之后,TableView
的數(shù)據(jù)源的調(diào)整時(shí)序也發(fā)生了變化婶希,下圖左邊為iOS7.0
以及之前的時(shí)序榕暇,右邊為iOS8.0
以及之后的時(shí)序:
從上圖可以很容易的分析出,iOS8.0
之后在獲取cell對(duì)象之后會(huì)再次調(diào)用heightForRowAtIndexPath:
方法獲取行高喻杈,這也就意味著我們其實(shí)可以先創(chuàng)建cell
對(duì)象彤枢,之后再提供行高。具體方法我們可以在cell
類中添加layoutAttribute
屬性筒饰,記錄相應(yīng)的UIEdgeInsets
缴啡,然后在設(shè)置cell
真實(shí)高度的時(shí)候返回。iOS7.0
之前則必須在cell
對(duì)象處啊給你講愛(ài)你之前先獲得所有cell
的高度瓷们。
三业栅、減少SubViews
層級(jí)、異步繪制谬晕、避免離屏渲染碘裕、使用Hidden
隱藏圖層
1. 減少圖層層級(jí)數(shù)量
當(dāng)我們自定義某個(gè)
cell
,并在cell
上添加大量的系統(tǒng)控件后攒钳,在創(chuàng)建該cell
對(duì)象時(shí)系統(tǒng)會(huì)調(diào)用底層接口進(jìn)行繪制帮孔,大量的添加操作會(huì)消耗很大的資源同時(shí)會(huì)影響渲染的性能。
2. 異步繪制
解決因圖層層級(jí)多造成的性能問(wèn)題,我們可以通過(guò)
drawRect:
方法文兢,調(diào)用Core Graphics
框架中的API
進(jìn)行異步繪制晤斩,提高效率。drawRect:
本身是移步的姆坚。另外drawRect:
中大量的繪制也會(huì)造成內(nèi)存的增長(zhǎng)澳泵,可以使用CAShapeLayer
來(lái)代替。
3. 減少多于的繪制操作
在實(shí)現(xiàn)
drawRect:
方法的時(shí)候兼呵,他的rect參數(shù)就是我們需要繪制的區(qū)域兔辅,在rect范圍之外的區(qū)域不要繪制,否則會(huì)消耗相當(dāng)大的資源萍程。
4. 圖片加載時(shí)機(jī)選擇
首先在cell中添加圖片應(yīng)該盡量避免使用
imageWithNamed:
方法幢妄,因?yàn)樵摲椒〞?huì)將圖片緩存到內(nèi)存中。而且應(yīng)該使用imageWithContentsOfFile:
方法來(lái)替換茫负,該方法在圖片使用完后系統(tǒng)會(huì)自動(dòng)釋放資源蕉鸳,并不會(huì)緩存下來(lái)。另外結(jié)合SDWebImage
框架的使用可以顯著提高圖片加載的性能。
5. 避免動(dòng)態(tài)添加圖層
在cell中應(yīng)該盡量避免動(dòng)態(tài)創(chuàng)建圖層。在初始化
cell
的時(shí)候一并將所有的圖層預(yù)先創(chuàng)建好悠菜,通過(guò)hidden
屬性控制子圖層的顯示或隱藏椿疗,因?yàn)閱渭兊娘@示操作要比創(chuàng)建快得多刽脖。
6. 避免離屏渲染
什么是離屏渲染?我們知道iOS底層的渲染框架使用的是OpenGL ES
。OpenGL
中,GPU
渲染屏幕方式有兩種:當(dāng)前屏幕渲染(On-Screen Rendering
)和離屏渲染(Off-Screen Rendering
)乱凿。它們的區(qū)別是當(dāng)前屏幕渲染操作是在當(dāng)前屏幕緩沖區(qū)完成,而離屏渲染會(huì)在另外一個(gè)新開(kāi)辟的緩沖區(qū)完成渲染操作咽弦,開(kāi)啟離屏渲染的代價(jià)就是需要新開(kāi)辟一塊新的緩沖區(qū)徒蟆,在渲染的過(guò)程中還會(huì)多次切換上下文,這些都是很消耗性能的型型。
- 為圖層設(shè)置遮罩(
layer.mask
) - 設(shè)置圖層的
layer.masksToBounds/view.clipsToBounds
屬性為True
- 設(shè)置圖層的
layer.allowsGroupOpacity
的屬性為True
和layer.opacity
小于1.0 - 設(shè)置圖層陰影(
layer.shadow
) - 設(shè)置圖層的
layer.shouldRasterize
的屬性為True
- 具有
layer.cornerRadius
段审,layer.edgeAntialiasingMask
,layer.allowsAntialiasing
的圖層 - 文本(任何種類闹蒜,包括
UILabel
寺枉、CATextLayer
、Core Text
等) - 使用
CGContext
在drawRect:
方法中繪制
上述情況均會(huì)造成離屏渲染绷落。
7. 圖片圓角優(yōu)化
使用貝塞爾曲線 + Core Graphics
框架設(shè)置圓角
- (void)setImageCircularEdge:(UIImageView *)imageView {
//開(kāi)始對(duì)imageView進(jìn)行畫圖
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用貝塞爾曲線畫出一個(gè)圓形圖
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//結(jié)束畫圖
UIGraphicsEndImageContext();
}
使用貝塞爾曲線 + CAShapeLayer
設(shè)置圓角
- (void)setImageCircularEdge2:(UIImageView *)imageView {
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;
}
8. 圖片陰影優(yōu)化
- (void)setImageShadow:(UIImageView *)imageView {
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path=[UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
}
四姥闪、分屏加載數(shù)據(jù),預(yù)先異步請(qǐng)求數(shù)據(jù)
在我們的項(xiàng)目開(kāi)發(fā)中列表視圖的應(yīng)用很多砌烁,有時(shí)數(shù)據(jù)比較多的時(shí)候我們不可能一次加載所有數(shù)據(jù)筐喳,這樣子會(huì)導(dǎo)致內(nèi)存的暴漲,同時(shí)用戶不一定會(huì)瀏覽所有的信息,造成資源浪費(fèi)疏唾。這時(shí)我們可以通過(guò)分屏加載來(lái)解決這個(gè)問(wèn)題,比如第一次加載10條數(shù)據(jù)函似,當(dāng)我向上滑動(dòng)列表的時(shí)候通常我們會(huì)再次去請(qǐng)求數(shù)據(jù)接口獲取下一個(gè)10條數(shù)據(jù)槐脏。這個(gè)時(shí)候如果我們不做任何的處理,那么我會(huì)發(fā)現(xiàn)每次劃過(guò)10條數(shù)據(jù)的時(shí)候列表都需要停頓一下撇寞,等待數(shù)據(jù)加載顿天。這樣子我們的列表就表現(xiàn)的不是很流暢了,那么怎么解決這個(gè)問(wèn)題呢蔑担?
提前異步預(yù)加載數(shù)據(jù)牌废!第一次加載完10條數(shù)據(jù)之后可以再預(yù)先加載下10條數(shù)據(jù),當(dāng)劃過(guò)第10條數(shù)據(jù)時(shí)啤握,再請(qǐng)求下10條數(shù)據(jù)鸟缕。這樣子我們的列表就表現(xiàn)的很流暢了。
五排抬、滑動(dòng)TableView時(shí)懂从,按需加載內(nèi)容
有些情況下我們可能會(huì)去快速的滑動(dòng)列表,這時(shí)候其實(shí)會(huì)有大量的cell對(duì)象被創(chuàng)建蹲蒲、被重用番甩,其實(shí)我們可能只是去瀏覽列表停止的那一頁(yè)的上下一定范圍內(nèi)的信息,前面快速劃過(guò)的那些信息對(duì)我們來(lái)說(shuō)都是無(wú)用的届搁。有什么方法讓我們只去加載最后那頁(yè)的目標(biāo)范圍內(nèi)的列表數(shù)據(jù)呢缘薛?那就是通過(guò)ScrollView
的代理方法'scrollViewWillEndDragging:withVelocity:targetContentoffset:'來(lái)實(shí)現(xiàn)的。
#pragma mark - UIScrollViewDelegate
//按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過(guò)指定行數(shù)卡睦,只在目標(biāo)滾動(dòng)范圍的前后指定3行加載宴胧。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
NSIndexPath *targetPath = [_myTableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
NSIndexPath *firstVisiblePath = [[_myTableView indexPathsForVisibleRows] firstObject];
NSInteger skipCount = 8;
if (labs(firstVisiblePath.row - targetPath.row)> skipCount) {
NSArray *temp = [_myTableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, _myTableView.frame.size.width, _myTableView.frame.size.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y<0) {
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row+33) {
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-3 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-2 inSection:0]];
[arr addObject:[NSIndexPath indexPathForRow:indexPath.row-1 inSection:0]];
}
}
[_dataList addObjectsFromArray:arr];
}
}
targetContentOffset
是TableView
減速到停止的地方,velocity
表示速度向量么翰。
六牺汤、cell類中應(yīng)該避免請(qǐng)求網(wǎng)絡(luò)加載數(shù)據(jù)
如果確實(shí)有需求不可避免,可以將網(wǎng)絡(luò)加載任務(wù)添加到Runloop
中浩嫌,設(shè)置DefaultRunloopModule
模式檐迟。這樣子可以起到延遲加載的作用。
七码耐、在willDisplayCell:forRowAtIndexPath:代理方法中的綁定數(shù)據(jù)
初學(xué)iOS的時(shí)候追迟,各類教程以及書籍中都喜歡在cellForRowAtIndexPath:
方法中綁定數(shù)據(jù),然后此時(shí)的cell
其實(shí)還未顯示骚腥,該方法中包含了大量的布局敦间、繪制相關(guān)的操作。我們應(yīng)該在該方法中盡量簡(jiǎn)化我們自身的邏輯操作。這時(shí)我們可以使用在willDisplayCell:forRowAtIndePath:
方法中綁定數(shù)據(jù)廓块。
#pragma mark - UITableViewDataSource
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *cellIdentifier = @"MyTableViewCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
return cell;
}
#pragma mark - UITableViewDelegate
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSDictionary *dict = self.dataList[indexPath];
[cell updateData:dict];
}