iOS UITableView性能優(yōu)化 - 進(jìn)階篇(附實例)

目錄
  • 卡頓起因
  • 避免卡頓的常見優(yōu)化方法
  • 按需加載
  • 異步繪制
  • 延時加載圖片
序言

UITableView 是我們開發(fā)中常用的控件,所以掌握 UITableView 的相關(guān)優(yōu)化技巧就顯得至關(guān)重要了,本文有針對性的對 UITableView 各種優(yōu)化技巧做總結(jié)。

一 卡頓優(yōu)化,避免掉幀

在優(yōu)化之前,我們先了解一下成像的原理及卡頓的起因,這樣就知道該如何解決卡頓了。

CPU和 GPU

在屏幕成像的過程中,CPU和GPU起著至關(guān)重要的作用

  • CPU(Central Processing Unit,中央處理器)
    對象的創(chuàng)建和銷毀、對象屬性的調(diào)整、布局計算、文本的計算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制(Core Graphics)

  • GPU(Graphics Processing Unit,圖形處理器)
    紋理的渲染

下圖展示視圖如何顯示

image.png

在iOS中是雙緩沖機(jī)制,有前幀緩存、后幀緩存

1.1 屏幕成像原理
image.png
1.2 卡頓原因,解決,監(jiān)測

卡頓產(chǎn)生的原因:因為CPU或者GPU所花費的時間過長,導(dǎo)致垂直信號來的時候,CPU計算或者GPU渲染未完成,從而掉幀。

掉幀.png
  • 卡頓解決的主要思路

(1) 盡可能減少CPU、GPU資源消耗
(2) 按照60FPS的刷幀率,每隔16ms就會有一次VSync信號

我們從CPU和GPU兩方面入手進(jìn)行卡頓優(yōu)化

1.3 減輕CPU負(fù)荷

我們知道CPU的主要負(fù)責(zé)快速調(diào)度任務(wù),大量計算工作,所以在tableView快速滾動的過程中讓CPU的計算量降低是優(yōu)化應(yīng)該考慮的方向.下面總結(jié)了三個方面來盡可能的降低CPU計算:

1.3.1 提前計算好cell的高度,緩存在相應(yīng)的數(shù)據(jù)源模型中

我們知道tableView的代理回調(diào)方法中,先調(diào)用的是返回cell高度的方法,然后在返回實例化cell的方法.我們可以在返回cell高度時,提前計算好cell的高度,緩存到數(shù)據(jù)源模型中。

實例代碼如下:

  • viewController.m
// 生成模型數(shù)據(jù)的時候計算視圖的高度
- (NSArray *)getRandomData {
    NSMutableArray *models = [NSMutableArray array];
    int number = arc4random_uniform(30);
    for (int i = 0; i < 20 + number; i++) {
        NewsModel *model = [[NewsModel alloc] init];
        ......
        model.rowHeight = [self calculateNewsCellHeight:model];
        [models addObject:model];
    }
    return models.copy;
}

- (CGFloat)calculateNewsCellHeight:(NewsModel *)model {
    float contentHeight = 64;
    contentHeight += ([self getContentHeight:model.content] + 10);
    if (model.imgs.count > 0) {
        contentHeight += (kImgViewWH + 10);
    }
    return (contentHeight + 44 + 5);    // 64,10,10,44,5等都是固定高度
}

- (CGFloat)getContentHeight:(NSString *)content {
    CGSize size = [content boundingRectWithSize:CGSizeMake(kScreenWidth - 20, MAXFLOAT)
                                        options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
                                       attributes:@{NSFontAttributeName:[UIFont systemFontOfSize:16 ]}
                                          context:nil].size;
    return size.height;
}

#pragma mark - updateData

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    CalculateNewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.model = model;
    return cell;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    return model.rowHeight;
}
  • CalculateNewsCell.m
- (void)setModel:(NewsModel *)model {
    _model = model;
   
    self.contentLbe.text = model.content;
    [self.contentLbe fitSizeHeight];  // 不能使用sizeToFit
}
  • UILabel (Extension)
- (void)fitSizeHeight {
    [self fitSizeHeight:0];
}

- (void)fitSizeHeight:(float)padding {
    float srcWidth = self.width;
    [self sizeToFit];
    float srcHeight = self.height;
    self.width = srcWidth;
    self.height = srcHeight + padding * 2;
}

運行效果

緩存高度.gif

注意事項:
1.UITableViewCell視圖中給UILabel賦值完 text后,一定要調(diào)用fitSizeHeight方法,不能調(diào)用系統(tǒng)的sizeToFit方法,要不然高度會計算錯誤。

1.3.2 盡可能的減少storyboard,xib的使用

通過Interface知道xib或者storyboard本身就是一個xml文件,添加刪除控件必然中間多了一個encode/decode過程,增加了cpu的計算量。并且還要避免臃腫的 XIB 文件,因為XIB文件在主線程中進(jìn)行加載布局。當(dāng)用到一些自定義View或者XIB文件時,XIB的加載會把所有內(nèi)容加載進(jìn)來,如果XIB里面的一些控件并不會用到,這就可能造成一些資源的消耗浪費。

比如使用純代碼布局,使用masonry約束布局或者手動計算布局。本人就是完全拋棄了storyboard和xib。

示例代碼如下

  • CalculateNewsCell.h
@class NewsModel;
/// 緩存 cell 的高度
@interface CalculateNewsCell : UITableViewCell
/** model */
@property(nonatomic, strong)NewsModel *model;
@end
  • CalculateNewsCell.m
@interface CalculateNewsCell()
/** icon */
@property(nonatomic, strong)UIImageView *iconImgView;
......
@end

@implementation CalculateNewsCell

#define kImgViewWH (kScreenWidth - 20 - 15) / 4.0

- (instancetype)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier {
    self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
    if (self) {
        self.contentView.backgroundColor = [UIColor whiteColor];
        [self drawUI];
    }
    return self;
}

#pragma mark - drawUI

- (void)drawUI {
    self.contentView.width = kScreenWidth;
    self.iconImgView.y = 10;
    self.iconImgView.x = 10;
    [self.contentView addSubview:self.iconImgView];
      
    ......
}

#pragma mark - set

- (void)setModel:(NewsModel *)model {
    _model = model;
    [self.iconImgView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
    ......
}

#pragma mark - lazy

- (UIImageView *)iconImgView {
    if (_iconImgView == nil) {
        _iconImgView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 44, 44)];
        _iconImgView.layer.cornerRadius = 22;
        _iconImgView.layer.masksToBounds = YES;
    }
    return _iconImgView;
}
純代碼布局.png
1.3.3 滑動過程中盡量減少重新布局

自動布局就是給控件添加約束,約束最終還是轉(zhuǎn)換成frame。所以在滿足業(yè)務(wù)需求情況下,如果圖層層次較為復(fù)雜,要盡量減少自動布局約束,轉(zhuǎn)為手動計算布局,大量的約束重疊也會增加cpu的計算量。

比如,如果內(nèi)容相對固定,可以將UILabel的寬高寫死,只是更新其文本內(nèi)容即可

  • 實例代碼如下
// 避免重復(fù)計算,寬高寫死
UILabel *titleLbe = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, kScreenWidth - 200, 20)];

- (void)setModel:(NewsModel *)model {
    _model = model;
    ......
    // 更新文本內(nèi)容即可
    self.titleLbe.text = model.title;
}

效果如下:

避免 frame 頻繁計算.png

本示例中就是將頭像尺寸,標(biāo)題,副標(biāo)題,最下面的分享按鈕,評論按鈕,點贊按鈕等視圖尺寸固定,只是更新內(nèi)容即可,避免了 CPU 的計算。

二 按需加載

當(dāng)滑動 UITableView 時,按需加載對應(yīng)的內(nèi)容

#pragma mark - UIScrollViewDelegate

// 按需加載 - 如果目標(biāo)行與當(dāng)前行相差超過指定行數(shù),只在目標(biāo)滾動范圍的前后指定3行加載。
- (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset {
    NSIndexPath *ip = [self.tableView indexPathForRowAtPoint:CGPointMake(0, targetContentOffset->y)];   // 停止拖拽后,預(yù)計滑動停止后到的偏移量
    NSIndexPath *cip = [[self.tableView indexPathsForVisibleRows] firstObject]; // 當(dāng)前可視區(qū)域內(nèi) cell 組
    NSInteger skipCount = 8;
    NSLog(@"targetContentOffset = %f",targetContentOffset->y);
    NSLog(@"indexPathForRowAtPoint = %@",ip);
    NSLog(@"visibleRows = %@",[self.tableView indexPathsForVisibleRows]);
    if (labs(cip.row - ip.row) > skipCount) {   // labs-返回 x 的絕對值,進(jìn)入該方法,說明滑動太厲害了,預(yù)計停留位置與當(dāng)前可視區(qū)域范圍內(nèi)差 8 個cell 以上了.
        // 拖拽停止滑動停止后,即將顯示的 cell 索引組
        NSArray *temp = [self.tableView indexPathsForRowsInRect:CGRectMake(0, targetContentOffset->y, self.tableView.width, self.tableView.height)];
        NSMutableArray *arrM = [NSMutableArray arrayWithArray:temp];
        NSLog(@"temp = %@",temp);
        if (velocity.y < 0) {   // 向上滑動-即加載更多數(shù)據(jù)
            NSIndexPath *indexPath = [temp lastObject];
            if (indexPath.row + 3 < self.dataSource.count) {    // 滑動停止后出現(xiàn)的 cell 索引仍在數(shù)據(jù)源范圍之內(nèi)
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 1 inSection:0]];
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 2 inSection:0]];
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row + 3 inSection:0]];
            }
        } else {    // 向下滑動-加載之前的數(shù)據(jù)
            NSIndexPath *indexPath = [temp firstObject];
            if (indexPath.row > 3) {
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 3 inSection:0]];
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 2 inSection:0]];
                [arrM addObject:[NSIndexPath indexPathForRow:indexPath.row - 1 inSection:0]];
            }
        }
        
        [self.needLoadArray addObjectsFromArray:arrM];
    }
}

運行結(jié)果如下

按需加載.png

運行效果如下:

按需加載.gif
  • UITableViewDataSource 數(shù)據(jù)源
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NeedLoadNewsCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    [self drawCell:cell withIndexPath:indexPath];
    cell.delegate = self;   // VC作為Cell視圖的代理對象
    return cell;
}

// 按需繪制
- (void)drawCell:(NeedLoadNewsCell *)cell withIndexPath:(NSIndexPath *)indexPath{
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    [cell clear];
    cell.model = model;
    // 如果當(dāng)前 cell 不在需要繪制的cell 中,則直接 pass
    if (self.needLoadArray.count > 0 && [self.needLoadArray indexOfObject:indexPath] == NSNotFound) {
        [cell clear];
        return;
    }
    if (_scrollToToping) {
        return;
    }
    [cell draw];
}
  • 一鍵返回至頂部方法判斷
// 開始滾動到頂部
- (BOOL)scrollViewShouldScrollToTop:(UIScrollView *)scrollView {
    _scrollToToping = YES;
    return YES;
}

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView {
    _scrollToToping = NO;
    [self loadContent];
}

// 已經(jīng)滾動到頂部
- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView {
    _scrollToToping = NO;
    [self loadContent];
}

- (void)loadContent {
    if (_scrollToToping) {
        return;
    }
    if (self.tableView.indexPathsForVisibleRows.count <= 0) {
        return;
    }
    if (self.tableView.visibleCells && self.tableView.visibleCells.count > 0) {
        for (NeedLoadNewsCell *cell in [self.tableView.visibleCells copy]) {
            [cell draw];
        }
    }
}

更多詳細(xì)代碼參考鏈接項目中NeedLoadViewControllerNeedLoadNewsCell類的實現(xiàn)

三 異步繪制

當(dāng)視圖層級比較多的時候,可以采用異步繪制的方式,通過UIGraphics將內(nèi)容繪制然后生成一張圖片進(jìn)行展示。

實現(xiàn)步驟如下

4.1 處理數(shù)據(jù)源

當(dāng)我們請求到了數(shù)據(jù)后,需要根據(jù)后端返回的數(shù)據(jù),根據(jù)內(nèi)容將布局計算出來,后面再進(jìn)行繪制,部分代碼示例如下

// 計算 frame
// 1.內(nèi)容 + 圖片預(yù)覽
{
    NSString *content = [NSString stringWithFormat:@"%@: %@ %@",model.specialWord,model.content,model.link];
    float width = kScreenWidth - SIZE_GAP_LEFT * 2;
    CGSize size =  [content sizeWithConstrainedToWidth:width fromFont:FontWithSize(SIZE_FONT_SUBCONTENT) lineSpace:5];
    NSInteger sizeHeight = size.height + .5;
    model.textFrame = CGRectMake(SIZE_GAP_LEFT, SIZE_GAP_BIG + 64, width, sizeHeight);
    sizeHeight += SIZE_GAP_BIG * 2;
    
    if (model.imgs.count > 0) { // 圖片
        sizeHeight += (SIZE_GAP_IMG + SIZE_IMAGE + SIZE_GAP_IMG);
    }
    sizeHeight += SIZE_GAP_BIG;
    model.contentFrame = CGRectMake(0, 64, kScreenWidth, sizeHeight);
}
  • sizeWithConstrainedToWidth:(float)width fromFont:(UIFont *)font1 lineSpace:(float)lineSpace
- (CGSize)sizeWithConstrainedToWidth:(float)width fromFont:(UIFont *)font1 lineSpace:(float)lineSpace{
    return [self sizeWithConstrainedToSize:CGSizeMake(width, CGFLOAT_MAX) fromFont:font1 lineSpace:lineSpace];
}

- (CGSize)sizeWithConstrainedToSize:(CGSize)size fromFont:(UIFont *)font1 lineSpace:(float)lineSpace{
    CGFloat minimumLineHeight = font1.pointSize,maximumLineHeight = minimumLineHeight, linespace = lineSpace;
    CTFontRef font = CTFontCreateWithName((__bridge CFStringRef)font1.fontName,font1.pointSize,NULL);
    CTLineBreakMode lineBreakMode = kCTLineBreakByWordWrapping;
    //Apply paragraph settings
    CTTextAlignment alignment = kCTLeftTextAlignment;
    CTParagraphStyleRef style = CTParagraphStyleCreate((CTParagraphStyleSetting[6]){
        {kCTParagraphStyleSpecifierAlignment, sizeof(alignment), &alignment},
        {kCTParagraphStyleSpecifierMinimumLineHeight,sizeof(minimumLineHeight),&minimumLineHeight},
        {kCTParagraphStyleSpecifierMaximumLineHeight,sizeof(maximumLineHeight),&maximumLineHeight},
        {kCTParagraphStyleSpecifierMaximumLineSpacing, sizeof(linespace), &linespace},
        {kCTParagraphStyleSpecifierMinimumLineSpacing, sizeof(linespace), &linespace},
        {kCTParagraphStyleSpecifierLineBreakMode,sizeof(CTLineBreakMode),&lineBreakMode}
    },6);
    NSDictionary* attributes = [NSDictionary dictionaryWithObjectsAndKeys:(__bridge id)font,(NSString*)kCTFontAttributeName,(__bridge id)style,(NSString*)kCTParagraphStyleAttributeName,nil];
    NSMutableAttributedString *string = [[NSMutableAttributedString alloc] initWithString:self attributes:attributes];
    //    [self clearEmoji:string start:0 font:font1];
    CFAttributedStringRef attributedString = (__bridge CFAttributedStringRef)string;
    CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedString);
    CGSize result = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [string length]), NULL, size, NULL);
    CFRelease(framesetter);
    CFRelease(font);
    CFRelease(style);
    string = nil;
    attributes = nil;
    return result;
}
4.2 異步繪制數(shù)據(jù)

在賦值數(shù)據(jù)模型里面進(jìn)行內(nèi)容的繪制

#pragma mark - set

- (void)setModel:(NewsModel *)model {
    _model = model;
    
    [self.iconImgView sd_setImageWithURL:[NSURL URLWithString:model.icon]];
    
    if (_drawed) {
        return;
    }
    NSUInteger flag = _drawColorFlag;
    _drawed = YES;
    
    // 開始異步繪制
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        // 先開啟一個上下文
        UIGraphicsBeginImageContextWithOptions(model.totalFrame.size, YES, 0);
        CGContextRef context = UIGraphicsGetCurrentContext();
        
        // 整個 cell區(qū)域
        [[UIColor colorWithRed:250/255.0 green:250/255.0 blue:250/255.0 alpha:1] set];
        CGContextFillRect(context, model.totalFrame);
        
        // 先繪制內(nèi)容的背景視圖
        [[UIColor colorWithRed:243/255.0 green:243/255.0 blue:243/255.0 alpha:1] set];
        CGContextFillRect(context, model.contentFrame);
        
        // 內(nèi)容視圖上方分割線
        [[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
        CGContextFillRect(context, CGRectMake(0, model.contentFrame.origin.y, model.contentFrame.size.width, 0.5));
        
        // title + subTitle
        {
            // title
            float leftX = SIZE_GAP_LEFT + SIZE_AVATAR + SIZE_GAP_BIG;
            float x = leftX;
            float y = (SIZE_AVATAR - (SIZE_FONT_NAME + SIZE_FONT_SUBTITLE + 6)) * 0.5 - 2 + SIZE_GAP_TOP + SIZE_GAP_SMALL - 5;
            [model.title drawInContext:context
                           withPosition:CGPointMake(x, y)
                                andFont:FontWithSize(SIZE_FONT_NAME)
                           andTextColor:[UIColor colorWithRed:106/255.0 green:140/255.0 blue:181/255.0 alpha:1]
                              andHeight:model.totalFrame.size.height];
            
            // subTitle
            y += (SIZE_FONT_NAME + 5);
            float fromX = leftX;
            float titleWidth = kScreenWidth - leftX;
            [model.subTitle drawInContext:context
                              withPosition:CGPointMake(fromX, y)
                                   andFont:FontWithSize(SIZE_FONT_SUBTITLE)
                              andTextColor:[UIColor colorWithRed:178/255.0 green:178/255.0 blue:178/255.0 alpha:1]
                                 andHeight:model.totalFrame.size.height
                                  andWidth:titleWidth];
        }
        
        // 點贊+評論按鈕 - 頂部分割線
        [[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
        CGContextFillRect(context, CGRectMake(0, model.contentFrame.origin.y + model.contentFrame.size.height, kScreenWidth, 0.5));
        
        // 點贊+評論按鈕 - 底部分割線
        [[UIColor colorWithRed:200/255.0 green:200/255.0 blue:200/255.0 alpha:1] set];
        CGContextFillRect(context, CGRectMake(0, model.contentFrame.origin.y + model.contentFrame.size.height + 43, kScreenWidth, 0.5));
        
        // 生成圖片
        UIImage *temp = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
        dispatch_async(dispatch_get_main_queue(), ^{
            if (flag == _drawColorFlag) {
                self.contentImgView.frame = model.totalFrame;
                self.contentImgView.image = nil;
                self.contentImgView.image = temp;
            }
        });
    });
    
    [self drawText];
    [self loadThumb];
    [self setData];
}

其中我們用了一個大神封裝好的 VVeboLabel,詳細(xì)源碼項目鏈接中有,有需要自行查閱。

- (void)drawText {
    if (label == nil) {
        [self addLabel];
    }
    label.frame = _model.textFrame;
    [label setText:[NSString stringWithFormat:@"%@: %@ %@",_model.specialWord,_model.content,_model.link]];
}

在調(diào)用setModel:賦值數(shù)據(jù)之前,我們需要做清除數(shù)據(jù)的操作

/// 清空視圖
- (void)clear {
    if (!_drawed) {
        return;
    }
    self.contentImgView.frame = CGRectZero;
    self.contentImgView.image = nil;
    
    [label clear];
    // 清除圖片
    for (UIImageView *imgView in self.imgListView.subviews) {
        [imgView sd_cancelCurrentAnimationImagesLoad];
    }
    self.imgListView.hidden = YES;
    _drawColorFlag = arc4random();
    _drawed = NO;
}

運行效果如下

1.gif
四 延時加載圖片

Runloop每次循環(huán)都會對你界面上的UI繪制一遍,主要是速度快,我們看不出來,當(dāng)界面中出現(xiàn)高清的圖片時因為繪制的慢,就會導(dǎo)致卡頓。 所以我們監(jiān)聽runloop的狀態(tài),每次即將休眠的時候,即處于kCFRunLoopBeforeWaiting狀態(tài)時才去繪制加載圖片。

核心代碼如下

  • 監(jiān)聽 runloop 狀態(tài)并且執(zhí)行當(dāng) runloop 處于kCFRunLoopBeforeWaiting時需要執(zhí)行的代碼塊
// 監(jiān)聽 runloop 狀態(tài)
- (void)addRunloopObserver {
    // 獲取當(dāng)前 runloop
    //獲得當(dāng)前線程的runloop,因為我們現(xiàn)在操作都是在主線程,這個方法就是得到主線程的runloop
    CFRunLoopRef runloop = CFRunLoopGetCurrent();

    //定義一個觀察者,這是一個結(jié)構(gòu)體
    CFRunLoopObserverContext context = {
        0,
        (__bridge void *)(self),
        &CFRetain,
        &CFRelease,
        NULL
    };

    // 定義一個觀察者
    static CFRunLoopObserverRef defaultModeObsever;
    // 創(chuàng)建觀察者
    defaultModeObsever = CFRunLoopObserverCreate(NULL,
                                                 kCFRunLoopBeforeWaiting,   // 觀察runloop等待的時候就是處于NSDefaultRunLoopMode模式的時候
                                                 YES,   // 是否重復(fù)觀察
                                                 NSIntegerMax - 999,
                                                 &Callback, // 回掉方法,就是處于NSDefaultRunLoopMode時候要執(zhí)行的方法
                                                 &context);
    
    // 添加當(dāng)前 RunLoop 的觀察者
    CFRunLoopAddObserver(runloop, defaultModeObsever, kCFRunLoopDefaultMode);
    //c語言有creat 就需要release
    CFRelease(defaultModeObsever);
}

// 每次 runloop 回調(diào)執(zhí)行代碼塊
static void Callback(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    DelayLoadImgViewController *vc = (__bridge DelayLoadImgViewController *)(info);  // 這個info就是我們在context里面放的self參數(shù)
    
    if (vc.tasks.count == 0) {
        return;
    }
    
    BOOL result = NO;
    while (result == NO && vc.tasks.count) {
        NSLog(@"開始執(zhí)行加載圖片總?cè)蝿?wù)數(shù):%d",vc.tasks.count);
        // 取出任務(wù)
        RunloopBlock unit = vc.tasks.firstObject;
        // 執(zhí)行任務(wù)
        result = unit();
        // d干掉第一個任務(wù)
        [vc.tasks removeObjectAtIndex:0];
    }
  • 將加載繪制圖片丟到任務(wù)中
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    NewsModel *model = [self.dataSource objectAtIndex:indexPath.row];
    DelayLoadImgCell *cell = [tableView dequeueReusableCellWithIdentifier:cellId];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.model = model;
    // 將加載繪制圖片操作丟到任務(wù)中去
    [self addTask:^BOOL{
        [cell drawImg];
        return YES;
    }];
    return cell;
}
  • UITableViewCell 中繪制圖片的方法
/// 繪制圖片
- (void)drawImg {
    if (_model.imgs.count > 0) {
        __block float posX = 0;
        [_model.imgs enumerateObjectsUsingBlock:^(NSString *obj, NSUInteger idx, BOOL *stop) {
            UIImageView *imgView = [[UIImageView alloc] initWithFrame:CGRectMake(posX, 0, kImgViewWH, kImgViewWH)];
            [imgView sd_setImageWithURL:[NSURL URLWithString:obj]];
            imgView.layer.cornerRadius = 5;
            imgView.layer.masksToBounds = YES;
            
            [self.imgListView addSubview:imgView];
            posX += (5 + kImgViewWH);
            if (idx >= 3) {
                *stop = YES;
            }
        }];
    }
}

運行效果如下

1.gif

控制臺輸出

image.png

每次當(dāng)我們滑動時,圖片不顯示,因為這個時候runloop不處于kCFRunLoopBeforeWaiting狀態(tài)。當(dāng)我們停止拖拽滑動時,runloop 處于kCFRunLoopBeforeWaiting狀態(tài),然后加載繪制圖片。

更多詳細(xì)代碼參考項目鏈接中的DelayLoadImgViewController


本文參考開源項目 VVeboTableViewDemo


項目鏈接地址 - tableViewPerformance

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

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

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