UITableView優(yōu)化

個(gè)人理解UITableView的優(yōu)化主要從UITableView的數(shù)據(jù)源方法著手。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

經(jīng)過測試我們可以發(fā)現(xiàn)heightForRowAtIndexPath方法在滑動UITableView的時(shí)候會調(diào)用很多次,cellForRowAtIndexPath也同樣。
所以我們優(yōu)化的方向基本上就是從這兩個(gè)高頻代理方法著手。

1.正確地使用UITableViewCell的重用機(jī)制
UITableView最核心的思想就是 UITableViewCell 的重用機(jī)制。UITableView 只會創(chuàng)建一屏幕(或一屏幕多一點(diǎn))的 UITableViewCell ,每當(dāng) cell 滑出屏幕范圍時(shí),就會放入到一重用池當(dāng)中,當(dāng)要顯示新的 cell 時(shí),先去重用池中取,若沒有可用的,才會重新創(chuàng)建。這樣可以極大的減少內(nèi)存的開銷。

2.減少在這兩個(gè)代理方法中創(chuàng)建復(fù)雜大對象處理和耗時(shí)操作。

3.CoreGraphics思路:
cell不和用戶交互的控件,可以考慮使用drawRect進(jìn)行繪制;
使用圓形圖片時(shí),采用CoreGraphics進(jìn)行裁剪,不要使用layer.cornerRadius方式(離屏渲染);
imgView寬高出現(xiàn)小數(shù)點(diǎn),造成鋸齒效果,離屏渲染,盡量避免;
imgView圖片過大需要壓縮。

4.使用xib布局cell的情況下,對heightForRowAtIndexPath代理方法進(jìn)行優(yōu)化。
對于固定cell高度就沒什么可優(yōu)化的了。
主要對于不定高度cell,可以進(jìn)行優(yōu)化。

最早期的方式當(dāng)然是獲取數(shù)據(jù),根據(jù)數(shù)據(jù)計(jì)算出數(shù)據(jù)高度,然后綁定到模型上。
缺點(diǎn)顯而易見,就是計(jì)算比較麻煩,且容易多次計(jì)算消耗性能。

iOS6,7的處理方法是根據(jù)IB約束和自動布局獲取到cell動態(tài)高度。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static ZSTableViewCell *cell = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    cell = (ZSTableViewCell*)[tableView dequeueReusableCellWithIdentifier:@"zSTableViewCell"];
    });
   CGFloat height = [cell calulateHeightWithtTitle:[self.datas objectAtIndex:indexPath.row] desrip:[self.datas objectAtIndex:indexPath.row]];
}

-(CGFloat)calulateHeightWithtTitle:(NSString*)title desrip:(NSString*)descrip
{
    CGFloat preMaxWaith =[UIScreen mainScreen].bounds.size.width-40;
    [self.content setPreferredMaxLayoutWidth:preMaxWaith];

    [self.content layoutIfNeeded];
    [self.content setText:descrip];
    [self.contentView layoutIfNeeded];
    CGSize size = [self.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

    return size.height+1;
}

iOS8以后,只需要設(shè)置兩個(gè)屬性就行。
這兩個(gè)屬性優(yōu)先級低于heightForRowAtIndexPath代理方法,如果實(shí)現(xiàn)代理方法,那么這兩個(gè)屬性就失效了,所以實(shí)現(xiàn)高度自動化就不需要實(shí)現(xiàn)代理方法。

self.tableview.estimatedRowHeight=60;
self.tableview.rowHeight=UITableViewAutomaticDimension;

所有這些都有一個(gè)缺點(diǎn),就是在不斷滑動UITableView的時(shí)候,再次計(jì)算高度。那么,我們不如把已經(jīng)算好的動態(tài)高度緩存下來,所以FDTemplateLayoutCell框架也就應(yīng)運(yùn)而生了。

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   CGFloat height = [tableView fd_heightForCellWithIdentifier:@"zSTableViewCell" cacheByIndexPath:indexPath configuration:^(id cell) {

       ZSTableViewCell* zscell=(ZSTableViewCell*)cell;

        zscell.contentStr=self.datas[indexPath.row];

   }];
    return height;
}

參考文章
http://www.itdecent.cn/p/8b662ce3c9a6

5.不使用xib布局cell的情況下,對cellForRowAtIndexPath方法使用異步繪制。

原理:
當(dāng)我們獲取到數(shù)據(jù)源的時(shí)候,我們需要對數(shù)據(jù)源進(jìn)行計(jì)算處理,計(jì)算出UI繪制所需要的屬性比如寬高、顏色等等,UIKit操作往往都是在主線程進(jìn)行的,我們?nèi)绻谧泳€程利用CoreGraphics進(jìn)行計(jì)算,主線程做最后渲染,會提高性能。

在繪制時(shí),對于不需要響應(yīng)觸摸事件的控件,我們應(yīng)該盡量避免創(chuàng)建UIView對象,取而代之的是使用更為輕量的CALayer,并且對于一個(gè)layer包含多個(gè)subLayer的情況時(shí),我們可以通過圖層預(yù)合成的方法,將多個(gè)subLayer合成渲染成一張圖片,通過上述的處理,不僅能減少CPU在創(chuàng)建UIKit對象的消耗,還能減少GPU在合成和渲染上的消耗,內(nèi)存的占用也會少很多。

類似這樣:

-(void)draw
{
    //異步計(jì)算UI控件的顏色文字圖片大小尺寸數(shù)據(jù),然后在主線程上全部渲染到圖片上
    CGRect rect = self.bounds;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{

        CGPoint point = CGPointMake(150, 150);
        UIGraphicsBeginImageContextWithOptions(rect.size, NO, 0);
        CGContextRef ctx = UIGraphicsGetCurrentContext();
        CGContextSetRGBFillColor(ctx, 0.5, 0.5, 0.5, 1);
        CGContextFillRect(ctx, CGRectMake(0, 0, 200, 200));
        NSMutableDictionary * dict =[NSMutableDictionary dictionary];
        [dict setObject:[UIFont systemFontOfSize:15] forKey:NSFontAttributeName];
        [dict setObject:[UIColor redColor] forKey:NSForegroundColorAttributeName];
        [@"內(nèi)容區(qū)域" drawInRect:CGRectMake(0, 0, 200, 200) withAttributes:dict];
        
        [[UIImage imageNamed:@"release_driver"] drawAtPoint:point];
        UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        //CGContextRelease(ctx);
        
        dispatch_async(dispatch_get_main_queue(), ^{
           
            self.bgImg.frame=rect;
            self.bgImg.image=temp;
        });

    });
    
}

成熟的異步渲染框架是YYKit的YYAsyncLayer和Facebook的AsyncDisplayKit

這里使用YYAsyncLayer進(jìn)行演示:
ViewController:

@interface ViewController ()<UITableViewDelegate,UITableViewDataSource>

@property (weak, nonatomic) IBOutlet UITableView *tableview;

@property(nonatomic,strong) NSMutableArray * datas;

@property(nonatomic,strong) NSMutableArray * layouts;

@end

@implementation ViewController

- (void)viewDidLoad {
    
    [super viewDidLoad];
    
    
}


-(NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
   
    return self.datas.count;
}

-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
   
    NSString * contentStr = self.datas[indexPath.row];

    NSDictionary * dict=[NSDictionary dictionaryWithObject:[UIFont systemFontOfSize:15] forKey:NSFontAttributeName];

    CGRect rect = [contentStr boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil];

    return rect.size.height;
}

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString * cellID=@"zSTableViewCell";
    
    ZSTableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellID];
    
    if(cell==nil)
    {
        cell=[[ZSTableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellID];
    }
    
    cell.layout=self.layouts[indexPath.row];
    
    return cell;
}

-(NSMutableArray*)datas
{
    if(_datas==nil)
    {
        _datas=[NSMutableArray array];
        
        [_datas addObject:@"正題:"];
        
        [_datas addObject:@"本文開陳述,廢話少說直入正題:"];
        
        [_datas addObject:@"本文主展開陳述,廢話少說直入正題:"];
        
        [_datas addObject:@"本文主要基予iOS適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高題:"];
        
        [_datas addObject:@"本文"];
        
        [_datas addObject:@"本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:"];
        
        [_datas addObject:@"UITableView控件可能是iOS中大家最常用的控件了(滾動視圖、cell重用、卡頓優(yōu)化),今天要討論的不是這些高大上的話題,今天的話題只是cell高度的計(jì)算。"];
        
        [_datas addObject:@"UITableView控件可能是iOS中大家最常用的控件了(滾動視圖、cell重用、卡頓優(yōu)化),今天要討論的不是這些高大上的話題,今天的話題只是cell高度的計(jì)算。"];
        
        [_datas addObject:@"本"];
        
        
        [_datas addObject:@"高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITabl"];
        
        [_datas addObject:@"本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:"];
        
        [_datas addObject:@"本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題"];
        
        [_datas addObject:@"本文主要基予iOS UITabl"];
        
        [_datas addObject:@"本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題"];
        
        [_datas addObject:@"本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:本文主要基予iOS UITableViewCell 高度自適應(yīng)計(jì)算問題展開陳述,廢話少說直入正題:,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題,廢話少說直入正題"];
        
        [_datas addObject:@"本文主要基予iOS UITabl本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS"];
        
        [_datas addObject:@"本文主要基予本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主要基予iOS本文主說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:要基予iOSiOS UITabl"];
        
        [_datas addObject:@"本文主要基說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:予iOS UITabl"];
        
        [_datas addObject:@"本文主要基說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:予iOS UITabl"];
        
        [_datas addObject:@"本文主要基說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:予iOS UITabl"];
        
        [_datas addObject:@"本文主要說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:說到手動計(jì)算內(nèi)容的高度,其實(shí)在cell里面大多是計(jì)算一些UILabel具體的寬高,根據(jù)內(nèi)容計(jì)算UILabel對應(yīng)的寬高,看下具體的API:基予iOS UITabl"];
    }
    
    return _datas;
}


-(NSMutableArray *)layouts
{
    if(_layouts==nil)
    {
        _layouts=[NSMutableArray array];
        
        for (int i=0; i<self.datas.count; i++) {
            
            NSMutableDictionary * dict=[NSMutableDictionary dictionaryWithObject:[UIFont systemFontOfSize:15] forKey:NSFontAttributeName];
            
            CGRect rect = [self.datas[i] boundingRectWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:dict context:nil];
            
            YYTextContainer *container = [YYTextContainer containerWithSize:CGSizeMake([UIScreen mainScreen].bounds.size.width, rect.size.width)];
            
            
            NSMutableAttributedString *text = [[NSMutableAttributedString alloc] initWithString:self.datas[i]];
            text.font = [UIFont systemFontOfSize:15];
            text.strokeColor = [UIColor blackColor];
            
            YYTextLayout *layout = [YYTextLayout layoutWithContainer:container text:text];
            
            [_layouts addObject:layout];
        }
    }
    
    return _layouts;
}

ZSTableViewCell:

@interface ZSTableViewCell()
{
    UIImageView * contentView;
    
    YYLabel * yyLabel;
}
@end

@implementation ZSTableViewCell


-(instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
     if(self=[super initWithStyle:style reuseIdentifier:reuseIdentifier])
    {

        yyLabel=[[YYLabel alloc]init];
        yyLabel.font = [UIFont systemFontOfSize:15];
        yyLabel.numberOfLines =0;
        yyLabel.displaysAsynchronously = YES; /// enable async display
        
        [self.contentView addSubview:yyLabel];
    }
    
    return self;
}



-(void)setLayout:(YYTextLayout *)layout
{
    _layout=layout;
    yyLabel.frame=layout.textBoundingRect;
    yyLabel.layer.contents = nil;
    yyLabel.textLayout=layout;
}

VVeboTableViewDemo實(shí)現(xiàn)異步渲染,同時(shí)優(yōu)化了滑動時(shí)加載數(shù)據(jù)的數(shù)據(jù)量。當(dāng)滑動時(shí),松開手指后,立刻計(jì)算出滑動停止時(shí) Cell 的位置,并預(yù)先繪制那個(gè)位置附近的幾個(gè) Cell,而忽略當(dāng)前滑動中的 Cell。忽略的代價(jià)就是快速滑動中會出現(xiàn)大量空白內(nèi)容。

這篇文章分析的比較全面 http://www.itdecent.cn/p/53c8056aba57

參考文章
http://www.itdecent.cn/p/af6b095aaaf3

最后編輯于
?著作權(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)容

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