- tableview 性能优化方法总览
- tableViewCell 复用
- 缓存 cell 高度
- 圆角优化
- 异步绘制
- 其余优化
- 一些优化方案的对比
heightForRowAtIndexPath:
是调用最频繁的方法)
tableViewCell复用介绍 tableView 内部有一个 cell池,里面放的就是你以前建立过的 cell 。内存丰富时会保存一些 UITableViewCell 对象放入到 cell 池,在须要调用的时候迅速的返回,而不用建立。内存吃紧时 cell 池会自动清理一些多余的 UITableViewCell 对象。至于有多少 cell ,这个内部会自动控制。
注意:重取出来的 cell 是有可能捆绑过数据或者加过子视图的,因此,若是有必要,要清除数据(如 label 的边框),从而使其显示正确的内容。html
tableviewCell 复用的方法
dequeueReusableCellWithIdentifier:forIndexPath:
(iOS6引入)ios
// 必须与register方法配套使用,不然返回的cell可能为nil,会crash
[slef.myTableView registerClass:[MyCell class] forCellReuseIdentifier:NSStringFromClass([MyCell class])];
MyCell* cell = [tableView dequeueReusableCellWithIdentifier:NSStringFromClass([MyCell class]) forIndexPath:indexPath];
复制代码
注册不一样类型的 cell 或者不复用,此处以不复用为例git
@property (nonatomic, strong) NSMutableDictionary *cellDic;//放cell的标识符
// 每次先从字典中根据IndexPath取出惟一标识符
NSString *identifier = [_cellDic objectForKey:[NSString stringWithFormat:@"%@", indexPath]];
// 若是取出的惟一标示符不存在,则初始化惟一标示符,并将其存入字典中,对应惟一标示符注册Cell
if (identifier == nil) {
identifier = [NSString stringWithFormat:@"%@%@", @"cell", [NSString stringWithFormat:@"%@", indexPath]];
[_cellDic setValue:identifier forKey:[NSString stringWithFormat:@"%@", indexPath]];
// 注册Cell
[self.tableview registerClass:[MyCell class] forCellWithReuseIdentifier:identifier];
}
MyCell *cell = [tableView dequeueReusableCellWithReuseIdentifier:identifier forIndexPath:indexPath];
复制代码
UITableView 复用机制原理:
查看UITableView头文件,会找到NSMutableArray *visiableCells,和NSMutableDictionary *reusableTableCells两个结构。其中visiableCells用来存储当前UITableView显示的cell,reusableTableCells用来存储已经用'identify'缓存的cell。当UITableView滚动的时候,会先在reusableTableCells中根据identify找是否有有已经缓存的cell,若是有直接用,没有再去初始化。(TableView显示之初,reusableTableCells为空,那么tableView dequeueReusableCellWithIdentifier:CellIdentifier返回nil。开始的cell都是经过[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]来建立,并且cellForRowAtIndexPath只是调用最大显示cell数的次数)github
@property (nonatomic, strong) NSMutableDictionary *heightAtIndexPath;//缓存高度所用字典
#pragma mark - UITableViewDelegate-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath]; if(height){
return height.floatValue;
}else {
return 100;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{ NSNumber *height = @(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
复制代码
fd_heightForCellWithIdentifier: configuration:
方法会根据 identifier 以及 configuration block 提供一个和 cell 布局相同的 template layout cell,并将其传入 fd_systemFittingHeightForConfiguratedCell:
这个私有方法返回计算出的高度。主要使用技术为 runtime 。OpenGL中,GPU屏幕渲染有如下两种方式: On-Screen Rendering:意思是当前屏幕渲染,指的是GPU的渲染操做是在当前用于显示的屏幕缓冲区进行。 Off-Screen Rendering:意思就是咱们说的离屏渲染了,指的是GPU在当前屏幕缓冲区之外新开辟一个缓冲区进行渲染操做。缓存
相比于当前屏幕渲染,离屏渲染的代价是很高的,主要体如今两个方面:性能优化
离屏渲染触发条件bash
UIImageView *imageView = [[UIImageView alloc]initWithFrame:CGRectMake(100, 100, 100, 100)];
imageView.image = [UIImage imageNamed:@"myImg"];
//开始对imageView进行画图
UIGraphicsBeginImageContextWithOptions(imageView.bounds.size, NO, 1.0);
//使用贝塞尔曲线画出一个圆形图
[[UIBezierPath bezierPathWithRoundedRect:imageView.bounds cornerRadius:imageView.frame.size.width] addClip];
[imageView drawRect:imageView.bounds];
imageView.image = UIGraphicsGetImageFromCurrentImageContext();
//结束画图
UIGraphicsEndImageContext();
[self.view addSubview:imageView];
复制代码
UIImageView *imageView = [[UIImageViewalloc]initWithFrame:CGRectMake(100,100,100,100)];
imageView.image = [UIImageimageNamed:@"myImg"];
UIBezierPath *maskPath = [UIBezierPathbezierPathWithRoundedRect:imageView.boundsbyRoundingCorners:UIRectCornerAllCornerscornerRadii:imageView.bounds.size];
CAShapeLayer *maskLayer = [[CAShapeLayer alloc]init];
//设置大小
maskLayer.frame=imageView.bounds;
//设置图形样子
maskLayer.path=maskPath.CGPath;
imageView.layer.mask=maskLayer;
[self.viewaddSubview:imageView];
复制代码
对于方案2须要解释的是: CAShapeLayer继承于CALayer,可使用CALayer的全部属性值;CAShapeLayer须要贝塞尔曲线配合使用才有意义(也就是说才有效果)使用CAShapeLayer(属于CoreAnimation)与贝塞尔曲线能够实现不在view的drawRect(继承于CoreGraphics走的是CPU,消耗的性能较大)方法中画出一些想要的图形CAShapeLayer动画渲染直接提交到手机的GPU当中,相较于view的drawRect方法使用CPU渲染而言,其效率极高,能大大优化内存使用状况。 总的来讲就是用CAShapeLayer的内存消耗少,渲染速度快,建议使用优化方案2。数据结构
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN
@interface AsyncLabel : UIView
@property (nonatomic, copy) NSString *asynText;
@property (nonatomic, strong) UIFont *asynFont;
@property (nonatomic, strong) UIColor *asynBGColor;
@end
NS_ASSUME_NONNULL_END
#import "AsyncLabel.h"
#import <CoreText/CoreText.h>
@implementation AsyncLabel
- (void)displayLayer:(CALayer *)layer {
/**
除了在drawRect方法中, 其余地方获取context须要本身建立[https://www.jianshu.com/p/86f025f06d62]
coreText用法简介:[https://www.cnblogs.com/purple-sweet-pottoes/p/5109413.html]
*/
CGSize size = self.bounds.size;;
CGFloat scale = [UIScreen mainScreen].scale;
///异步绘制:切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
///获取当前上下文
CGContextRef context = UIGraphicsGetCurrentContext();
///将坐标系反转
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
///文本沿着Y轴移动
CGContextTranslateCTM(context, 0, size.height);
///文本反转成context坐标系
CGContextScaleCTM(context, 1.0, -1.0);
///建立绘制区域
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, size.width, size.height));
///建立须要绘制的文字
NSMutableAttributedString *attStr = [[NSMutableAttributedString alloc] initWithString:self.asynText];
[attStr addAttribute:NSFontAttributeName value:self.asynFont range:NSMakeRange(0, self.asynText.length)];
[attStr addAttribute:NSBackgroundColorAttributeName value:self.asynBGColor range:NSMakeRange(0, self.asynText.length)];
///根据attStr生成CTFramesetterRef
CTFramesetterRef frameSetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attStr);
CTFrameRef frame = CTFramesetterCreateFrame(frameSetter, CFRangeMake(0, attStr.length), path, NULL);
///将frame的内容绘制到content中
CTFrameDraw(frame, context);
UIImage *getImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
///子线程完成工做, 切换到主线程展现
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (__bridge id)getImg.CGImage;
});
});
}
@end
#import "ViewController.h"
#import "AsyncLabel.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
AsyncLabel *asLabel = [[AsyncLabel alloc] initWithFrame:CGRectMake(50, 100, 200, 200)];
asLabel.backgroundColor = [UIColor cyanColor];
asLabel.asynBGColor = [UIColor greenColor];
asLabel.asynFont = [UIFont systemFontOfSize:16 weight:20];
asLabel.asynText = @"学习异步绘制相关知识点, 学习异步绘制相关知识点";
[self.view addSubview:asLabel];
///不调用的话不会触发 displayLayer方法
[asLabel.layer setNeedsDisplay];
}
@end
复制代码
- (void)loadData{
// 开辟子线程处理数据
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
// 处理数据 coding...
// 返回主线程处理
dispatch_async(dispatch_get_main_queue(), ^{
[self.mainTableView reloadData];
});
});
复制代码
不要作多余的绘制工做
在实现drawRect:的时候,它的rect参数就是须要绘制的区域,这个区域以外的不须要进行绘制。 例如上例中,就能够用CGRectIntersectsRect、CGRectIntersection或CGRectContainsRect判断是否须要绘制image和text,而后再调用绘制方法。框架
如图,这个label显示的内容由model的两个参数(时间、千米数)拼接而成,咱们习惯在cell里model的set方法中这样赋值异步
//时间
NSDateFormatter* formatter = [[NSDateFormatter alloc] init];
formatter.dateStyle = NSDateFormatterMediumStyle;
formatter.timeStyle = NSDateFormatterShortStyle;
[formatter setDateFormat:@"yyyy年MM月"];
NSDate* date = [NSDate dateWithTimeIntervalSince1970:[model.licenseTime intValue]];
NSString* licenseTimeString = [formatter stringFromDate:date];
//千米数
NSString *travelMileageString = (model.travelMileage != nil && ![model.travelMileage isEqualToString:@""]) ? [NSString stringWithFormat:@"%@万千米",model.travelMileage] : @"里程暂无";
//赋值给label.text
self.carDescribeLabel.text = [NSString stringWithFormat:@"%@ / %@",licenseTimeString,travelMileageString];
复制代码
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return 15.0 + 80.0 + 15.0;
}
复制代码
修改成static float ROW_HEIGHT = 15.0 + 80.0 + 15.0;
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return ROW_HEIGHT;
}
复制代码
固然这不是减小对象的建立,而是减小了计算的次数,减小了频繁调用方法里的逻辑,从而达到更快的速度。文章推荐:
VVeboTableViewDemo
性能优化-UITableView的优化使用
iOS 保持界面流畅的技巧