iOS-UITableView重用機制和性能優(yōu)化、

簡介:

UITableView我想大家都不陌生,他是UIKit一個重要組件。可以用來展示數(shù)據(jù)列表,或者靈活使用進行頁面布局。
其使用中遵循MVC模式,數(shù)據(jù)模型(NSObject)、視圖(UIView)、控制器(UITableViewController)分離。 點擊前往Github下載Demo

官方文檔使用:

// 注冊方式
[self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:@"identifier"] ;
//返回每一組的每一行顯示什么內(nèi)容
- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    
    // 定義一個重用標示,用static修飾。就放在了內(nèi)存的靜態(tài)區(qū)了。
    static NSString *identifier = @"Cell";
    
    // 緩存池中尋找是否有可以重用的cell
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
    
    // 如果緩存池中沒有ID,創(chuàng)建一個cell,并給它一個重用標示
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    }
    
    // 設置數(shù)據(jù),賦值給cell
    
    return cell;
}

為了做到顯示和數(shù)據(jù)分離,iOS TableView的實現(xiàn)并不是為每一個數(shù)據(jù)創(chuàng)建cell,而是創(chuàng)建屏幕可顯示最大個數(shù)+1的cell,對cell做單獨顯示配置,來達到既不影響顯示效果,又能充分節(jié)約內(nèi)容的目的。


重用機制原理:

image

機制

假設虛線范圍是屏幕的顯示區(qū)域,整個屏幕里面每個Cell的identifier是一樣的。

A2、A6 的Cell有一部分是在屏幕內(nèi)。

A3、A4、A5的Cell全部在屏幕內(nèi)。

系統(tǒng)會創(chuàng)建當前屏幕Cell個數(shù)+1的Cell,A1在屏幕外,現(xiàn)在它就被放到了重用池;

向上滑動時候,新的CellA7就會去重用池里面根據(jù)指定indentifier取出A1存放的Cell。

就如同盤子使用了之后,洗完可以繼續(xù)使用。

作用:

避免大量創(chuàng)建實例對象,減少Memory Warning內(nèi)存的消耗甚至Crash掉,從而提高滑動流暢性,提高用戶體驗!


UITableView的性能優(yōu)化

一般開發(fā)過程中遇到的幾個重要的問題:

cellForRowAtIndexPath:方法中處理了過多的業(yè)務。
cellheight動態(tài)變化時計算方式不對。
tableviewCellsubview層級太復雜,做了大量透明處理。

1、cell的數(shù)據(jù)綁定

在使用的過程中我們注意到cellForRowAtIndexPath:中為每一個cell綁定數(shù)據(jù),在實際調(diào)用中cellForRowAtIndexPath:時候cell還沒有被顯示出來,為了提高效率應該把綁定數(shù)據(jù)操作放在cell顯示后再執(zhí)行,可以在tableView:willDisplayCell:forRowAtIndexPath:方法中綁定數(shù)據(jù)。

- (UITableViewCell *) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    static NSString *identifier = @"Cell"; 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier]; 
    if (cell == nil) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
    } 
    return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
    // model類在setter方法中進行設置也可。 
    cell.textLabel.text = @"這里進行數(shù)據(jù)綁定";
}

2、cell高度計算

cell的高度分為兩種,一種是常用的固定高度,一種類似微博首頁的動態(tài)計算高度。

(1)固定高度Cell

self.tableView.rowHeight = 55;

這個方法指定了所有cell高度都是55,rowHeight默認的值是44。對于定高cell,直接采用上面方式給定高度,不需要實現(xiàn)tableView:heightForRowAtIndexPath: 以節(jié)省不必要的計算和開銷【重要】。

(2)動態(tài)計算高度Cell
動態(tài)計算高度,我們需要用到heightForRowAtIndexPath方法,這個代理方法實現(xiàn)之后,rowHeight設置將會變成無效。

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return 55 ;
}

思考:
類似微博項目根據(jù)傳入那些動態(tài)的元素(文字,圖片等),然后返回計算后的高度。這樣沒有問題,只是計算量很復雜,每次reloadData,【UITableView在每次reloadData的時候都要刷新所有cell高度,如果你有100行cell,代理就會執(zhí)行100次cell高度,而不是屏幕顯示cell的數(shù)量的高度。】很消耗性能。

方法1:
iOS8之后,有了self-sizing cell的概念,cell可以自己算出高度,不過使用起來會有三個條件:

  • 使用Autolayout進行UI布局約束(要求cell.contentView的四條邊都與內(nèi)部元素有約束關系)。
  • 指定TableView的estimatedRowHeight屬性的默認范圍值。
  • 指定TableView的rowHeight屬性為UITableViewAutomaticDimension。
   self.tableView.estimatedRowHeight = 55.0;
   self.tableView.rowHeight = UITableViewAutomaticDimension;

方法2:
賦值和計算布局分離,在服務器異步獲得數(shù)據(jù)之后,根據(jù)數(shù)據(jù)源計算出對應的布局,并緩存到數(shù)據(jù)源中。這樣在tableView:heightForRowAtIndexPath:方法中就直接返回高度,而不需要每次都計算了。
新建一個dataModel類,新建

@property (nonatomic, assign) CGFloat cellViewH;

- (CGFloat)cellViewH {
    // 寫入你的業(yè)務邏輯根據(jù)不同內(nèi)容變化高度
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
        dataModel *model = self.[indexPath.row];
        return model.cellViewH;
}

這樣的方法基本能滿足簡單界面,但是針對朋友圈圖文混排,這樣還是需要繼續(xù)優(yōu)化的。

方法3:

TODO

3、頁面

方法1、圓角優(yōu)化

我們一般設置圓角的方式如下:

self.imageView.layer.cornerRadius=CGFloat(10);
self.imageView.layer.masksToBounds=YES;

這種處理的渲染機制是GPU在當前屏幕緩沖區(qū)外新開辟一個渲染緩沖區(qū)進行工作,也就是離屏渲染。如果圓角操作達到一定數(shù)量,會觸發(fā)緩沖區(qū)的頻繁合并和上下文頻繁切換,性能的代價會宏觀地表現(xiàn)在用戶體驗上——掉幀。

方案1:使用貝塞爾曲線UIBezierPath和Core Graphics框架畫出一個圓角

  UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
   imageView.image = [UIImage imageNamed:@"myImg"];
   //開始對imageView進行畫圖
   UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
   //使用貝塞爾曲線畫出一個圓形圖
   [[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
   [imageView drawRect:imageView.bounds];
   imageView.image = UIGraphicsGetImageFromCurrentImageContext();
   UIGraphicsEndImageContext();
   [self.view addSubview:imageView];

方案2:使用CAShapeLayer和UIBezierPath設置圓角

    UIImageView *imageView=[[UIImageView alloc]initWithFrame:CGRectMake(100,100,100,100)];
    imageView.image=[UIImage imageNamed:@"myImg"];
    UIBezierPath *maskPath=[UIBezierPath bezierPathWithRoundedRect:imageView.bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:imageView.bounds.size];
    CAShapeLayer *maskLayer=[[CAShapeLayer alloc]init];
    maskLayer.frame=imageView.bounds;
    maskLayer.path=maskPath.CGPath;
    imageView.layer.mask=maskLayer;
    [self.view addSubview:imageView];

方法2、滑動時按需加載對應的內(nèi)容

如果目標行與當前行相差超過指定行數(shù),只在目標滾動范圍的前后指定3行加載。

-(void)scrollViewWillEndDragging:(UIScrollView *)scrollViewwithVelocity(CGPoint)velocitytargetContentOffset(inoutCGPoint *)targetContentOffset{
   // 代碼待整理 
}

方法3、定義一種(盡量少)類型的Cell,擅長使用hidden隱藏(顯示)

分析cell結構,盡可能將相同內(nèi)容抽取到一種樣式的cell,這樣雖然cell的體積會大很多,但是數(shù)量不會多。這樣的好處:

* 減少代碼量,減少XIB文件的數(shù)量,容易修改和維護。
* 基于cell重用,運行時鋪滿屏幕所需cell數(shù)量固定N個,如果只有一種cell,那就是N個cell實例,如果M中cell,可能會是MN個cell實例,相比之下占用更多內(nèi)存。

方法4、使用不透明視圖

不透明的視圖可以極大地提高渲染的速度。因此如非必要,可以將table cell及其子視圖的opaque屬性設為YES(默認值UIButton內(nèi)部的label的opaque默認值都是NO])。
Cell中不要使用clearColor,無背景色,透明度也不要設置為0。

方法5、使用局部更新

如果只是更新某組的話,使用reloadSection進行局部更新

方法6、不要給cell動態(tài)添加subView

在初始化cell的時候就將所有需要展示的添加完畢,然后根據(jù)需要來設置hide屬性顯示和隱藏。

方法7、異步化UI,不要阻塞主線程

我們時常會看到這樣一個現(xiàn)象,就是加載時整個頁面卡住不動,怎么點都沒用,仿佛死機了一般。原因是主線程被阻塞了。所以對于網(wǎng)路數(shù)據(jù)的請求或者圖片的加載,我們可以開啟多線程,將耗時操作放到子線程中進行,異步化操作。

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

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

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