前言
- UITableView是我們經(jīng)常會使用的控件,那么關(guān)于這塊的優(yōu)化還是很有必要,網(wǎng)上關(guān)于這塊優(yōu)化的資料很多,其實(shí)核心本質(zhì)還是降低 CPU和GPU 的工作來提升性能
CPU:對象的創(chuàng)建和銷毀、對象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制
GPU:紋理的渲染
CPU層面優(yōu)化
1.用輕量級對象
比如用不到事件處理的地方,可以考慮使用 CALayer 取代 UIView
CALayer * imageLayer = [CALayer layer];
imageLayer.bounds = CGRectMake(0,0,200,100);
imageLayer.position = CGPointMake(200,200);
imageLayer.contents = (id)[UIImage imageNamed:@"xx.jpg"].CGImage;
imageLayer.contentsGravity = kCAGravityResizeAspect;
[tableCell.contentView.layer addSublayer:imageLayer];
2.不要頻繁地調(diào)用 UIView 的相關(guān)屬性
比如 frame、bounds、transform 等屬性,盡量減少不必要的修改
不要給UITableViewCell動(dòng)態(tài)添加subView,可以在初始化UITableViewCell的時(shí)候就將所有需要展示的添加完畢,然后根據(jù)需要來設(shè)置hidden屬性顯示和隱藏
3.提前計(jì)算好布局
UITableViewCell高度計(jì)算主要分為兩種,一種固定高度,另外一種動(dòng)態(tài)高度.
固定高度:
rowHeight高度默認(rèn)44
對于固定高度直接采用self.tableView.rowHeight = 77比tableView:heightForRowAtIndexPath:更高效
動(dòng)態(tài)高度:
采用tableView:heightForRowAtIndexPath:這種代理方式,設(shè)置這種代理之后rowHeight則無效,需要滿足以下三個(gè)條件
- 使用Autolayout進(jìn)行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關(guān)系)
- 指定TableView的
estimatedRowHeight屬性的默認(rèn)值 - 指定TableView的
rowHeight屬性為UITableViewAutomaticDimension
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44;
除了提高cell高度的計(jì)算效率之外,對于已經(jīng)計(jì)算出的高度,我們需要進(jìn)行緩存
4.直接設(shè)置frame
Autolayout 會比直接設(shè)置 frame 消耗更多的 CPU 資源
5.圖片尺寸合適
圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
圖片通過contentMode處理顯示,對tableview滾動(dòng)速度同樣會造成影響
- 從網(wǎng)絡(luò)下載圖片后先根據(jù)需要顯示的圖片大小切/壓縮成合適大小的圖,每次只顯示處理過大小的圖片,當(dāng)查看大圖時(shí)在顯示大圖。
- 服務(wù)器直接返回預(yù)處理好的小圖和大圖以及對應(yīng)的尺寸最好
/// 根據(jù)特定的區(qū)域?qū)D片進(jìn)行裁剪
+ (UIImage*)kj_cutImageWithImage:(UIImage*)image Frame:(CGRect)cropRect{
return ({
CGImageRef tmp = CGImageCreateWithImageInRect([image CGImage], cropRect);
UIImage *newImage = [UIImage imageWithCGImage:tmp scale:image.scale orientation:image.imageOrientation];
CGImageRelease(tmp);
newImage;
});
}
6.控制最大并發(fā)數(shù)量
控制一下線程的最大并發(fā)數(shù)量,當(dāng)下載線程數(shù)超過2時(shí),會顯著影響主線程的性能。因此在使用ASIHTTPRequest時(shí),可以用一個(gè)NSOperationQueue來維護(hù)下載請求,并將其最大線程數(shù)目maxConcurrentOperationCount。
NSURLRequest可以配合 GCD進(jìn)階技巧分享 來實(shí)現(xiàn),或者使用NSURLConnection的setDelegateQueue:方法。
當(dāng)然在不需要響應(yīng)用戶請求時(shí),也可以增加下載線程數(shù)來加快下載速度:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if (!decelerate) self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
self.queue.maxConcurrentOperationCount = 2;
}
7.子線程處理
盡量把耗時(shí)的操作放到子線程
- 文本處理(尺寸計(jì)算、繪制)
- 圖片處理(解碼、繪制)
8.預(yù)渲染圖像
顯示圖像時(shí),解壓和重采樣會消耗很多CPU時(shí)間,
當(dāng)有圖像時(shí),在bitmap context先將其畫一遍,導(dǎo)出成UIImage對象,然后再繪制到屏幕,這會大大提高渲染速度,
- (void)awakeFromNib {
if (self.image == nil) {
self.image = [UIImage imageNamed:@"xxx"];
UIGraphicsBeginImageContextWithOptions(imageSize, YES, 0);
[image drawInRect:imageRect];
self.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
}
}
9.異步繪制
異步繪制,就是異步在畫布上繪制內(nèi)容,將復(fù)雜的繪制過程放到后臺線程中執(zhí)行,然后在主線程顯示
// 異步繪制,切換至子線程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
// TODO:draw in context...
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = imgRef;
});
});
這篇文章 iOS-UIView異步繪制 介紹的滿詳細(xì)
當(dāng)然還是少不了YY大神的佳作,iOS 保持界面流暢的技巧(轉(zhuǎn)載)
10.按需求加載
滑動(dòng)UITableView時(shí),按需加載對應(yīng)的內(nèi)容
//按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù),只在目標(biāo)滾動(dòng)范圍的前后指定3行加載。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{
NSIndexPath *ip = [self indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];
NSIndexPath *cip = [[self indexPathsForVisibleRows] firstObject];
NSInteger skipCount = 10;
if (labs(cip.row-ip.row) > skipCount) {
NSArray *temp = [self indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.width, self.height)];
NSMutableArray *arr = [NSMutableArray arrayWithArray:temp];
if (velocity.y < 0) {
NSIndexPath *indexPath = [temp lastObject];
if (indexPath.row > 3) {
[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]];
}
}
[self.needLoadDatas addObjectsFromArray:arr];
}
}
還需要在tableView:cellForRowAtIndexPath:方法中加入判斷
if (self.needLoadDatas.count > 0 && [self.needLoadDatas indexOfObject:indexPath] == NSNotFound) {
//TODO:清理工作
return;
}
GPU層面優(yōu)化
1.避免短時(shí)間內(nèi)大量顯示圖片
盡可能將多張圖片合成一張進(jìn)行顯示
- RunLoop小操作
當(dāng)前線程是主線程時(shí),某些UI事件,比如ScrollView正在拖動(dòng),將會RunLoop切換成NSEventTrackingRunLoopMode模式,在這個(gè)模式下默認(rèn)的NSDefaultRunLoopMode模式中注冊的事件是不會執(zhí)行
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
KJTestModel *model = self.datas[indexPath.row];
if (model.iconImage) {
cell.imageView.image = model.iconImage;
}else{
NSDictionary *dict = @{@"imageView":cell.imageView,@"model":model};
[self performSelector:@selector(kj_loadImageView:) withObject:dict afterDelay:0.0 inModes:@[NSDefaultRunLoopMode]];
}
cell.nameLabel.text = model.name;
cell.IDLabel.hidden = model.remarkName == nil ? YES : NO;
cell.label.text = model.remarkName;
}
/// 下載圖片,并渲染到cell上顯示
- (void)kj_loadImageView:(NSDictionary*)dict{
UIImageView *imageView = dict[@"imageView"];
[imageView sd_setImageWithURL:model.avatar completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
KJTestModel *model = dict[@"model"];
model.iconImage = image;
}];
}
2.控制尺寸
GPU能處理的最大紋理尺寸是4096x4096,超過這個(gè)尺寸就會占用CPU資源進(jìn)行處理,所以紋理盡量不要超過這個(gè)尺寸
3.減少圖層混合操作
當(dāng)多個(gè)視圖疊加,放在上面的視圖是半透明的,那么這個(gè)時(shí)候GPU就要進(jìn)行混合,把透明的顏色加上放在下面的視圖的顏色混合之后得出一個(gè)顏色再顯示在屏幕上,這一步是消耗GPU資源
- UIView的backgroundColor不要設(shè)置為clearColor,最好設(shè)置和superView的backgroundColor顏色一樣。
- 圖片避免使用帶alpha通道的圖片
4.透明處理
減少透明的視圖,不透明的就設(shè)置opaque = YES
5.避免離屏渲染
離屏渲染就是在當(dāng)前屏幕緩沖區(qū)以外,新開辟一個(gè)緩沖區(qū)進(jìn)行操作
離屏渲染的整個(gè)過程,需要多次切換上下文環(huán)境,先是從當(dāng)前屏幕切換到離屏;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上,又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕
1 - 下面的情況或操作會引發(fā)離屏渲染
- 光柵化,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圓角,同時(shí)設(shè)置 layer.masksToBounds = YES 和 layer.cornerRadius > 0
- 陰影,layer.shadow
- layer.allowsGroupOpacity = YES 和 layer.opacity != 1
- 重寫drawRect方法
2 - 圓角優(yōu)化
這里主要其實(shí)就是解決同時(shí)設(shè)置layer.masksToBounds = YES 和layer.cornerRadius > 0就會產(chǎn)生的離屏渲染
其實(shí)我們在使用常規(guī)視圖切圓角時(shí),可以只使用view.layer.cornerRadius = 3.0,這時(shí)是不會產(chǎn)生離屏渲染
但是UIImageView這家伙有點(diǎn)特殊,切圓角時(shí)必須上面2句同時(shí)設(shè)置,則會產(chǎn)生離屏渲染,所以我們考慮通過 CoreGraphics 繪制裁剪圓角,或者叫美工提供圓角圖片
- (UIImage *)kj_ellipseImage{
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
CGContextClip(ctx);
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
鏤空圓形圖片覆蓋,此方法可以實(shí)現(xiàn)圓形頭像效果,這個(gè)也是極為高效的方法。缺點(diǎn)就是對視圖的背景有要求,單色背景效果就最為理想
3 - 陰影優(yōu)化
對于shadow,如果圖層是個(gè)簡單的幾何圖形或者圓角圖形,我們可以通過設(shè)置shadowPath來優(yōu)化性能,能大幅提高性能
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;
4 - 強(qiáng)制開啟光柵化
當(dāng)圖像混合了多個(gè)圖層,每次移動(dòng)時(shí),每一幀都要重新合成這些圖層,十分消耗性能,這時(shí)就可以選擇強(qiáng)制開啟光柵化layer.shouldRasterize = YES
當(dāng)我們開啟光柵化后,會在首次產(chǎn)生一個(gè)位圖緩存,當(dāng)再次使用時(shí)候就會復(fù)用這個(gè)緩存,但是如果圖層發(fā)生改變的時(shí)候就會重新產(chǎn)生位圖緩存。
所以這個(gè)功能一般不能用于UITableViewCell中,復(fù)用反而降低了性能。最好用于圖層較多的靜態(tài)內(nèi)容的圖形
5 - 優(yōu)化建議
- 使用中間透明圖片蒙上去達(dá)到圓角效果
- 使用ShadowPath指定layer陰影效果路徑
- 使用異步進(jìn)行l(wèi)ayer渲染
- 將UITableViewCell及其子視圖的opaque屬性設(shè)為YES,減少復(fù)雜圖層合成
- 盡量使用不包含透明alpha通道的圖片資源
- 盡量設(shè)置layer的大小值為整形值
- 背景色的alpha值應(yīng)該為1,例如不要使用clearColor
- 直接讓美工把圖片切成圓角進(jìn)行顯示,這是效率最高的一種方案
- 很多情況下用戶上傳圖片進(jìn)行顯示,可以讓服務(wù)端處理圓角
最后簡單介紹TableViewCell的部分常用屬性
| 功能 | API & Property |
|---|---|
| 設(shè)置分割線顏色 | [tableView setSeparatorColor:UIColor.orangeColor] |
| 設(shè)置分割線樣式 | tableView.separatorStyle = UITableViewCellSeparatorStyleSingleLine |
| 是否允許多選 | tableView.allowsMultipleSelection |
| 是否響應(yīng)點(diǎn)擊操作 | tableView.allowsSelection = YES |
| 返回選中的多行 | tableView.indexPathsForSelectedRows |
| 可見的行 | tableView.indexPathsForVisibleRows |