iOS UITableView性能優(yōu)化

前言

  • 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 = 77tableView: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í)行,然后在主線程顯示


image.png
// 異步繪制,切換至子線程
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 = YESlayer.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

UITableView性能優(yōu)化介紹就到此完畢,后面有相關(guān)再補(bǔ)充,寫文章不容易,還請點(diǎn)個(gè)小星星傳送門

備注:本文用到的部分函數(shù)方法和Demo,均來自三方庫KJEmitterView,如有需要的朋友可自行pod 'KJEmitterView'引入即可

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 在iOS應(yīng)用中,UITableView應(yīng)該是使用率最高的視圖之一了。iPod、時(shí)鐘、日歷、備忘錄、Mail、天氣、...
    劉光軍_MVP閱讀 3,837評論 5 15
  • 1.最常用的就是cell的重用, 注冊重用標(biāo)識符它的原理是,根據(jù)cell高度和tableView大小,確定界面上能...
    樹下敲代碼的超人閱讀 5,052評論 4 26
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,631評論 1 32
  • 在iOS應(yīng)用中,UITableView應(yīng)該是使用率最高的視圖之一了。iPod、時(shí)鐘、日歷、備忘錄、Mail、天氣、...
    baihualinxin閱讀 247評論 0 0
  • UITableView性能優(yōu)化1.使用不透明視圖:不透明的視圖可以極大地提高渲染的速度。因此如非必要,可以將tab...
    問題餓閱讀 300評論 0 1

友情鏈接更多精彩內(nèi)容