個(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