前言
- UITableView是我們經(jīng)常會使用的控件,那么關(guān)于這塊的優(yōu)化還是很有必要,網(wǎng)上關(guān)于這塊優(yōu)化的資料很多吻育,其實核心本質(zhì)還是降低 CPU和GPU 的工作來提升性能
CPU:對象的創(chuàng)建和銷毀做个、對象屬性的調(diào)整、布局計算秋茫、文本的計算和排版棉磨、圖片的格式轉(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動態(tài)添加subView衙傀,可以在初始化UITableViewCell的時候就將所有需要展示的添加完畢,然后根據(jù)需要來設(shè)置hidden
屬性顯示和隱藏
3.提前計算好布局
UITableViewCell高度計算主要分為兩種萨咕,一種固定高度统抬,另外一種動態(tài)高度.
固定高度:
rowHeight
高度默認(rèn)44
對于固定高度直接采用self.tableView.rowHeight = 77
比tableView:heightForRowAtIndexPath:
更高效
動態(tài)高度:
采用tableView:heightForRowAtIndexPath:
這種代理方式,設(shè)置這種代理之后rowHeight
則無效,需要滿足以下三個條件
- 使用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īng)計算出的高度钙畔,我們需要進(jìn)行緩存
4.直接設(shè)置frame
Autolayout 會比直接設(shè)置 frame 消耗更多的 CPU 資源
5.圖片尺寸合適
圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
圖片通過contentMode處理顯示,對tableview滾動速度同樣會造成影響
- 從網(wǎng)絡(luò)下載圖片后先根據(jù)需要顯示的圖片大小切/壓縮成合適大小的圖金麸,每次只顯示處理過大小的圖片擎析,當(dāng)查看大圖時在顯示大圖。
- 服務(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時揍魂,會顯著影響主線程的性能。因此在使用ASIHTTPRequest時棚瘟,可以用一個NSOperationQueue來維護(hù)下載請求现斋,并將其最大線程數(shù)目maxConcurrentOperationCount
。
NSURLRequest可以配合 GCD進(jìn)階技巧分享 來實現(xiàn)偎蘸,或者使用NSURLConnection的setDelegateQueue:
方法庄蹋。
當(dāng)然在不需要響應(yīng)用戶請求時,也可以增加下載線程數(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.子線程處理
盡量把耗時的操作放到子線程
- 文本處理(尺寸計算迷雪、繪制)
- 圖片處理(解碼限书、繪制)
8.預(yù)渲染圖像
顯示圖像時,解壓和重采樣會消耗很多CPU時間振乏,
當(dāng)有圖像時蔗包,在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.按需求加載
滑動UITableView時,按需加載對應(yīng)的內(nèi)容
//按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù)倡缠,只在目標(biāo)滾動范圍的前后指定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.避免短時間內(nèi)大量顯示圖片
盡可能將多張圖片合成一張進(jìn)行顯示
- RunLoop小操作
當(dāng)前線程是主線程時,某些UI事件昙沦,比如ScrollView正在拖動琢唾,將會RunLoop切換成NSEventTrackingRunLoopMode
模式,在這個模式下默認(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
采桃,超過這個尺寸就會占用CPU資源進(jìn)行處理懒熙,所以紋理盡量不要超過這個尺寸
3.減少圖層混合操作
當(dāng)多個視圖疊加,放在上面的視圖是半透明的普办,那么這個時候GPU就要進(jìn)行混合工扎,把透明的顏色加上放在下面的視圖的顏色混合之后得出一個顏色再顯示在屏幕上,這一步是消耗GPU資源
- UIView的backgroundColor不要設(shè)置為clearColor衔蹲,最好設(shè)置和superView的backgroundColor顏色一樣肢娘。
- 圖片避免使用帶alpha通道的圖片
4.透明處理
減少透明的視圖,不透明的就設(shè)置opaque = YES
5.避免離屏渲染
離屏渲染就是在當(dāng)前屏幕緩沖區(qū)以外踪危,新開辟一個緩沖區(qū)進(jìn)行操作
離屏渲染的整個過程蔬浙,需要多次切換上下文環(huán)境猪落,先是從當(dāng)前屏幕切換到離屏贞远;等到離屏渲染結(jié)束以后,將離屏緩沖區(qū)的渲染結(jié)果顯示到屏幕上笨忌,又需要將上下文環(huán)境從離屏切換到當(dāng)前屏幕
1 - 下面的情況或操作會引發(fā)離屏渲染
- 光柵化蓝仲,layer.shouldRasterize = YES
- 遮罩,layer.mask
- 圓角官疲,同時設(shè)置 layer.masksToBounds = YES 和 layer.cornerRadius > 0
- 陰影袱结,layer.shadow
- layer.allowsGroupOpacity = YES 和 layer.opacity != 1
- 重寫drawRect方法
2 - 圓角優(yōu)化
這里主要其實就是解決同時設(shè)置layer.masksToBounds = YES
和layer.cornerRadius > 0
就會產(chǎn)生的離屏渲染
其實我們在使用常規(guī)視圖切圓角時,可以只使用view.layer.cornerRadius = 3.0
途凫,這時是不會產(chǎn)生離屏渲染
但是UIImageView
這家伙有點特殊垢夹,切圓角時必須上面2句同時設(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;
}
鏤空圓形圖片覆蓋,此方法可以實現(xiàn)圓形頭像效果犀盟,這個也是極為高效的方法而晒。缺點就是對視圖的背景有要求,單色背景效果就最為理想
3 - 陰影優(yōu)化
對于shadow阅畴,如果圖層是個簡單的幾何圖形或者圓角圖形倡怎,我們可以通過設(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 - 強制開啟光柵化
當(dāng)圖像混合了多個圖層贱枣,每次移動時监署,每一幀都要重新合成這些圖層,十分消耗性能纽哥,這時就可以選擇強制開啟光柵化layer.shouldRasterize = YES
當(dāng)我們開啟光柵化后钠乏,會在首次產(chǎn)生一個位圖緩存,當(dāng)再次使用時候就會復(fù)用這個緩存昵仅,但是如果圖層發(fā)生改變的時候就會重新產(chǎn)生位圖緩存缓熟。
所以這個功能一般不能用于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)點擊操作 | tableView.allowsSelection = YES |
返回選中的多行 | tableView.indexPathsForSelectedRows |
可見的行 | tableView.indexPathsForVisibleRows |