iOS UITableView性能優(yōu)化

前言

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


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.按需求加載

滑動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 = YESlayer.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

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末尔许,一起剝皮案震驚了整個濱河市么鹤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌味廊,老刑警劉巖蒸甜,帶你破解...
    沈念sama閱讀 217,734評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異余佛,居然都是意外死亡柠新,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評論 3 394
  • 文/潘曉璐 我一進(jìn)店門辉巡,熙熙樓的掌柜王于貴愁眉苦臉地迎上來恨憎,“玉大人,你說我怎么就攤上這事郊楣°究遥” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評論 0 354
  • 文/不壞的土叔 我叫張陵痢甘,是天一觀的道長喇嘱。 經(jīng)常有香客問我,道長塞栅,這世上最難降的妖魔是什么者铜? 我笑而不...
    開封第一講書人閱讀 58,532評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮放椰,結(jié)果婚禮上作烟,老公的妹妹穿的比我還像新娘。我一直安慰自己砾医,他們只是感情好拿撩,可當(dāng)我...
    茶點故事閱讀 67,585評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著如蚜,像睡著了一般压恒。 火紅的嫁衣襯著肌膚如雪影暴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,462評論 1 302
  • 那天探赫,我揣著相機與錄音型宙,去河邊找鬼。 笑死伦吠,一個胖子當(dāng)著我的面吹牛妆兑,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播毛仪,決...
    沈念sama閱讀 40,262評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搁嗓,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了箱靴?” 一聲冷哼從身側(cè)響起腺逛,我...
    開封第一講書人閱讀 39,153評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎刨晴,沒想到半個月后屉来,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體路翻,經(jīng)...
    沈念sama閱讀 45,587評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡狈癞,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,792評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了茂契。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蝶桶。...
    茶點故事閱讀 39,919評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖掉冶,靈堂內(nèi)的尸體忽然破棺而出真竖,到底是詐尸還是另有隱情,我是刑警寧澤厌小,帶...
    沈念sama閱讀 35,635評論 5 345
  • 正文 年R本政府宣布恢共,位于F島的核電站,受9級特大地震影響璧亚,放射性物質(zhì)發(fā)生泄漏讨韭。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,237評論 3 329
  • 文/蒙蒙 一癣蟋、第九天 我趴在偏房一處隱蔽的房頂上張望透硝。 院中可真熱鬧,春花似錦疯搅、人聲如沸濒生。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽罪治。三九已至丽声,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間觉义,已是汗流浹背恒序。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留谁撼,地道東北人歧胁。 一個月前我還...
    沈念sama閱讀 48,048評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像厉碟,于是被迫代替她去往敵國和親喊巍。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,864評論 2 354

推薦閱讀更多精彩內(nèi)容

  • 在iOS應(yīng)用中箍鼓,UITableView應(yīng)該是使用率最高的視圖之一了崭参。iPod、時鐘款咖、日歷何暮、備忘錄、Mail铐殃、天氣海洼、...
    劉光軍_MVP閱讀 3,772評論 5 15
  • 1.最常用的就是cell的重用, 注冊重用標(biāo)識符它的原理是富腊,根據(jù)cell高度和tableView大小坏逢,確定界面上能...
    樹下敲代碼的超人閱讀 4,873評論 4 26
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,101評論 1 32
  • 在iOS應(yīng)用中,UITableView應(yīng)該是使用率最高的視圖之一了赘被。iPod是整、時鐘、日歷民假、備忘錄浮入、Mail、天氣羊异、...
    baihualinxin閱讀 213評論 0 0
  • UITableView性能優(yōu)化1.使用不透明視圖:不透明的視圖可以極大地提高渲染的速度事秀。因此如非必要,可以將tab...
    問題餓閱讀 278評論 0 1