1. 程式人生 > >效能優化-UITableView的優化使用

效能優化-UITableView的優化使用

UITableView的效能優化

1、使用reuseIdentifier來重用cells

2、快取cell的行高

(1)當cell的行高是固定的時,使用固定行高;

(2)當cell的行高是不固定時,根據內容進行計算後快取起來使用。第一次肯定會計算,後續使用快取時就避免了多次計算;高度的計算方法通常寫在自定義的cell中,呼叫時,既可以在設定cell高的代理方法中使用,也可以自定義的model中使用(且使用時,使用get方法處理);

(3)另外需要注意,cell的賦值和cell行高的計算需要分開,否則也會造成記憶體爆增,且介面異常的卡頓;

3、減少subviews的數量

(1)自定義的子檢視可以整合在形成一個整體的就整合成一個整體的子檢視;

(2)同時使用drawRect進行繪製(即將GPU的部分渲染轉接給CPU);

(3)異常繪製,且設定屬性"self.layer.drawsAsynchronously = YES;"

4、資料處理

(1)使用正確的資料結構來儲存資料;

(2)資料儘量採用區域性的section,或cellRow的重新整理,避免reloadData;

(3)大量資料操作時,使用非同步子執行緒處理,避免主執行緒中直接操作;

5、圖片處理

(1)使用非同步子執行緒處理,然後再返回主執行緒操作

(2)圖片快取處理,避免多次處理操作

(3)圖片圓角處理時,設定layer的shouldRasterize屬性為YES,可以將負載轉移給CPU

6、按需載入內容

(1)滑動操作時,只顯示目標範圍內的cell內容,顯示過的超出目標範圍內之後則進行清除;

(2)滑動過程中,不載入顯示圖片,停止時才載入顯示圖片;

7、使用drawRect,或CALayer進行文字或圖片的繪製

其他可能出現影響的處理:

1、如果cell內現實的內容來自web,使用非同步載入,快取請求結果

2、儘量使所有的view的opaque屬性為YES,包括cell自身,以提高檢視渲染速度(避免無用的alpha通道合成,降低GPU負載

3、避免漸變,圖片縮放的操作

4、使用shadowPath來畫陰影

5、儘量不使用cellForRowAtIndexPath:,如果你需要用到它,只用一次然後快取結果

示例Demo

未優化前:使用者操作時,耗時、介面卡頓、且記憶體異常。


優化後:使用者操作流暢,記憶體穩定。


優化過程:

(1)子執行緒異常處理資料

- (void)loadData
{
    // 開闢子執行緒處理資料
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 處理資料
        coding...
        // 返回主執行緒處理
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.mainTableView reloadData];
        });
    });
}

(2)cell複用

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSLog(@"3 %s", __func__);        
    
    TableViewPerformanceCell *cell = [tableView dequeueReusableCellWithIdentifier:identifierTableViewPerformanceCell];
    if (cell == nil)
    {
        cell = [[TableViewPerformanceCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifierTableViewPerformanceCell];
    }
    
    TableModel *model = self.mainArray[indexPath.row];
    cell.model = model;
       
    return cell;
}

(3)動態的cell行高快取

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableModel *model = self.mainArray[indexPath.row];
    CGFloat height = model.height;
    // 方法1 在代理方法中計算後賦值
//    if (height == 0.0)
//    {
//        height = [TableViewPerformanceCell heightWithModel:model];
//        
//        // 快取計算出來的高度,避免重複計算
//        model.height = height;
//    }
    
    NSLog(@"2 %s(%@:height = %@)", __func__, @(indexPath.row), @(height));
    
    return height;
}
// 自定義的mode資料型別中,定義屬性height,在cell行高代理方法中則可以直接使用
// 方法2 使用get方法獲取值
- (float)height
{
    if (_height == 0.0)
    {
        _height = [TableViewPerformanceCell heightWithModel:self];
    }
    
    return _height;
}

(3)自定義的cell中減少子檢視,將能整合的子檢視(a1,a2,a3...)整合形成一個獨立的子檢視A

#import <UIKit/UIKit.h>
#import "TableModel.h"

#import "TableTitleView.h"
#import "TableImageView.h"

static NSString *const identifierTableViewPerformanceCell = @"TableViewPerformanceCell";

@interface TableViewPerformanceCell : UITableViewCell

//@property (nonatomic, strong) UIImageView *iconImageView;
//@property (nonatomic, strong) UILabel *nameLabel;
//@property (nonatomic, strong) UILabel *timeLabel;
//@property (nonatomic, strong) UILabel *titleLabel;
//@property (nonatomic, strong) UIView *lineView;
// iconImageView/nameLabel/timeLabel/titleLabel/lineView整合成TableTitleView
@property (nonatomic, strong) TableTitleView *titleView;
@property (nonatomic, strong) UILabel *contentLabel;
//@property (nonatomic, strong) UIScrollView *imageScrollView;
// // imageScrollView中的所有圖片子檢視整合成TableImageView
@property (nonatomic, strong) TableImageView *imageview;

@property (nonatomic, strong) TableModel *model;

+ (CGFloat)heightWithModel:(TableModel *)model;

- (void)showFrame;

@end

對於不確定數量,且相同型別的子檢視

(a)可以採用先初始化,後使用的方法,使用前先進行初始值的設定(通常是預設固定數量);

(b)或採用使用時才進行初始化,使用前先進行對應父檢視中所有子檢視的移除;

// 初始化方法
- (void)setImageUI:(NSInteger)count
{
    for (int i = 0; i < count; i++)
    {
        UIImageView *imageView = [[UIImageView alloc] init];
        [self addSubview:imageView];
        imageView.backgroundColor = [UIColor colorWithWhite:0.2 alpha:0.1];
        // 預設初始值
        imageView.hidden = YES;
        imageView.frame = CGRectZero;
    }
}
// 重置初始值
- (void)clearImageUI
{
    for (NSInteger i = self.subviews.count; i > 0; i--)
    {
        UIImageView *imageview = [self.subviews objectAtIndex:i-1];
        
        // 使用時才建立,則移除
//        [imageview removeFromSuperview];
        
        // 或先建立後使用,則修改預設值
        imageview.frame = CGRectZero;
        imageview.hidden = YES;
    }
}
// 使用,根據情況設定真實值
- (void)reloadImages:(NSArray *)images
{
    // 重置初始值
    [self clearImageUI];
    
    if (images && 0 != images.count)
    {
        NSInteger count = images.count;
        
        // 重新整理資料時才建立,或初始化時已經建立
//        [self setImageUI:count];

        coding...
    }
}

(4)圖片處理採用快取,同時使用非同步子執行緒

// 開闢子執行緒處理圖片
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            
            // 圖片快取
            NSData *imageData = [[NSUserDefaults standardUserDefaults] objectForKey:_model.imageName];
            UIImage *image = [UIImage imageWithData:imageData];
            if (image == nil)
            {
                NSURL *imageUrl = [NSURL URLWithString:_model.imageName];
                NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
                image = [UIImage imageWithData:imageData];
                
                [[NSUserDefaults standardUserDefaults] setObject:imageData forKey:_model.imageName];
                [[NSUserDefaults standardUserDefaults] synchronize];
            }
            // 返回主執行緒
            dispatch_async(dispatch_get_main_queue(), ^{
//                self.iconImageView.image = image;
                self.titleView.iconImageView.image = image;
            });
});

(5)區域性重新整理資料,避免重新整理全部資料

// 重新整理所有section,cell的資訊
[self.mainTableView reloadData];
    
// 重新整理某個cell的資訊
NSIndexPath *reloadIndexPath = [NSIndexPath indexPathForRow:0 inSection:0];
NSArray *reloadArray = @[reloadIndexPath];
[self.mainTableView reloadRowsAtIndexPaths:reloadArray withRowAnimation:UITableViewRowAnimationNone];
    
// 重新整理某個section及其所有cell的資訊
NSIndexSet *reloadIndexSet = [NSIndexSet indexSetWithIndex:0];
[self.mainTableView reloadSections:reloadIndexSet withRowAnimation:UITableViewRowAnimationNone];

(6)使用drawRect繪製

效果圖


注意事項:

 1、自定義一個UIView,同時定義其drawRect方法,如drawRectContant,以用作自定義cell上的子檢視subview,否則繪製的文字或圖片在高亮選中時不會顯示;

 2、繪製文字或圖片時,呼叫重定義的drawRectContant方法;

 3、設定資料資訊時,重置cellframe,子檢視subviewframe,以及呼叫setNeedsDisplay方法;

- (void)drawUI
{
    self.contentView.frame = CGRectMake(0.0, 0.0, WidthScreen, _model.height);
    [self setNeedsDisplay];
    
    cellView.frame = self.contentView.bounds;
    [cellView setNeedsDisplay];
}
- (void)drawContantRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    // 背景色
    if (self.highlighted || self.selected)
    {
        CGContextSetFillColorWithColor(context, [UIColor greenColor].CGColor);
        CGContextFillRect(context, rect);
    }
    else
    {
        CGContextSetFillColorWithColor(context, [UIColor yellowColor].CGColor);
        CGContextFillRect(context, rect);
    }
    
    // 頭像
    // 圖片快取
    NSData *imageData = [[NSUserDefaults standardUserDefaults] objectForKey:_model.imageName];
    UIImage *image = [UIImage imageWithData:imageData];
    NSLog(@"1 image = %@", image);
    if (image == nil)
    {
        NSURL *imageUrl = [NSURL URLWithString:_model.imageName];
        NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
        image = [UIImage imageWithData:imageData];
        
        NSLog(@"2 image = %@", image);
        
        [[NSUserDefaults standardUserDefaults] setObject:imageData forKey:_model.imageName];
        [[NSUserDefaults standardUserDefaults] synchronize];
    }
    image = [self drawCircleImage:image size:sizeImage];
    [image drawInRect:CGRectMake(originXY, originXY, sizeImage, sizeImage) blendMode:kCGBlendModeNormal alpha:1.0];

    // 名稱
    [_model.name drawInRect:CGRectMake((originXY + sizeImage + originXY), originXY, (WidthScreen - originXY - sizeImage - originXY - originXY), textHeight) withAttributes:@{NSForegroundColorAttributeName:[UIColor orangeColor], NSFontAttributeName:[UIFont systemFontOfSize:13.0]}];
    
    // 時間
    [_model.time drawInRect:CGRectMake((originXY + sizeImage + originXY), (originXY + textHeight + originXY), (WidthScreen - originXY - sizeImage - originXY - originXY), textHeight) withAttributes:@{NSForegroundColorAttributeName:[UIColor redColor], NSFontAttributeName:[UIFont systemFontOfSize:13.0]}];
    
    // 標題
    [_model.title drawInRect:CGRectMake(originXY, (originXY + sizeImage + originXY), (WidthScreen - originXY * 2), textHeight) withAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor], NSFontAttributeName:[UIFont systemFontOfSize:13.0]}];
    
    // 分割線
    CGContextSetLineWidth(context, 0.5);
    CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
    CGContextMoveToPoint(context, originXY, (originXY + sizeImage + originXY + textHeight + originXY - 0.5));
    CGContextAddLineToPoint(context, (WidthScreen - originXY), (originXY + sizeImage + originXY + textHeight + originXY - 0.5));
    CGContextStrokePath(context);
    
    // 內容
    CGFloat contentHeight = [[self class] heightWithText:_model.content];
    [_model.content drawInRect:CGRectMake(originXY, (originXY + sizeImage + originXY + textHeight + originXY + originXY), (WidthScreen - originXY * 2), contentHeight) withAttributes:@{NSForegroundColorAttributeName:[UIColor blackColor], NSFontAttributeName:[UIFont systemFontOfSize:13.0]}];
    
    // 圖片
    if (_model.images && 0 != _model.images.count)
    {
        NSInteger count = _model.images.count;
 
        CGFloat originX = 0.0;
        CGFloat originY = 0.0;
        for (int index = 0; index < count; index++)
        {
            originX = ((index % 3) * (sizeImageContent + originXY) + originXY);
            if (0 == index % 3)
            {
                originX = originXY;
            }
            originY = ((index / 3) * (sizeImageContent + originXY) + (originXY + sizeImage + originXY + textHeight + originXY + originXY + contentHeight + originXY));
            
            // 圖片快取
            NSString *imageName = _model.images[index];
            NSData *imageData = [[NSUserDefaults standardUserDefaults] objectForKey:imageName];
            UIImage *image = [UIImage imageWithData:imageData];
            if (image == nil)
            {
                NSURL *imageUrl = [NSURL URLWithString:imageName];
                NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
                image = [UIImage imageWithData:imageData];
                
                [[NSUserDefaults standardUserDefaults] setObject:imageData forKey:imageName];
                [[NSUserDefaults standardUserDefaults] synchronize];
            }
            [image drawInRect:CGRectMake(originX, originY, sizeImageContent, sizeImageContent) blendMode:kCGBlendModeNormal alpha:1.0];
        }
    }
    
    // 分割線
    CGContextSetLineWidth(context, 0.5);
    CGContextSetStrokeColorWithColor(context, [UIColor blueColor].CGColor);
    CGContextMoveToPoint(context, 0.0, (_model.height - 0.5));
    CGContextAddLineToPoint(context, WidthScreen, (_model.height - 0.5));
    CGContextStrokePath(context);
}

// 圓角圖片
- (UIImage *)drawCircleImage:(UIImage *)image size:(CGFloat)size
{
    CGFloat side = MIN(size, size);
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(side, side), false, [UIScreen mainScreen].scale);
    CGContextAddPath(UIGraphicsGetCurrentContext(),
                     [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, side, side)].CGPath);
    CGContextClip(UIGraphicsGetCurrentContext());
    CGFloat marginX = -(size - side) / 2.f;
    CGFloat marginY = -(size - side) / 2.f;
    [image drawInRect:CGRectMake(marginX, marginY, size, size)];
    CGContextDrawPath(UIGraphicsGetCurrentContext(), kCGPathFillStroke);
    UIImage *output = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return output;
}