目錄
- 卡頓起因
- 避免卡頓的常見優(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,圖形處理器)
紋理的渲染
下圖展示視圖如何顯示

在iOS中是雙緩沖機(jī)制,有前幀緩存、后幀緩存
1.1 屏幕成像原理

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

- 卡頓解決的主要思路
(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;
}
運行效果

注意事項:
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;
}

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;
}
效果如下:

本示例中就是將頭像尺寸,標(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é)果如下

運行效果如下:

- 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ì)代碼參考鏈接項目中NeedLoadViewController和NeedLoadNewsCell類的實現(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;
}
運行效果如下

四 延時加載圖片
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;
}
}];
}
}
運行效果如下

控制臺輸出

每次當(dāng)我們滑動時,圖片不顯示,因為這個時候
runloop不處于kCFRunLoopBeforeWaiting狀態(tài)。當(dāng)我們停止拖拽滑動時,runloop 處于kCFRunLoopBeforeWaiting狀態(tài),然后加載繪制圖片。
更多詳細(xì)代碼參考項目鏈接中的DelayLoadImgViewController類
本文參考開源項目 VVeboTableViewDemo