您的位置:首页 > 产品设计 > UI/UE

性能优化-UITableView的优化使用

2017-02-13 17:13 232 查看
gitHub学习地址:https://github.com/potato512/SYDemo_TablePerformance

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、设置数据信息时,重置cell的frame,子视图subview的frame,以及调用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;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: