一些根底的优化
(一)CPU
1. 用轻量级对象
比方用不到事情处理的当地,能够考虑运用 CALayer 替代 UIView
CALayer * imageLayer = [CALayer layer];
imageLayer.bounds = CGRectMake(0,0,200,100);
imageLayer.position = CGPointMake(200,200);
imageLayer.contents = (id)[UIImage imageNamed:@"xx.jpg"].CGImage;
imageLayer.contentsGravity = kCAGravityResizeAspect;
[tableCell.contentView.layer addSublayer:imageLayer];
2. 不要频频地调用UIView的相关特点
比方 frame
、bounds
、transform
等特点,尽量削减不必要的修改
不要给UITableViewCell
动态增加subView
,能够在初始化UITableViewCell
的时分就将一切需求展现的增加结束,然后根据需求来设置hidden
特点显现和躲藏
3. 提早核算好布局
在滑动时,会不断调用heightForRowAtIndexPath:
,当Cell高度需求自适应时,每次回调都要核算高度,会导致UI卡顿。为了防止重复无意义的核算,需求缓存高度。
UITableViewCell
高度核算首要有两种,一种固定高度,另外一种动态高度。
固定高度:
rowHeight
高度默认44
关于固定高度直接选用self.tableView.rowHeight = 77
比tableView:heightForRowAtIndexPath:
更高效
动态高度:
选用tableView:heightForRowAtIndexPath:
这种署理方法,设置这种署理之后rowHeight
则无效,需求满意以下三个条件
- 运用Autolayout进行UI布局束缚(要求
cell.contentView
的四条边都与内部元素有束缚联系) - 指定TableView的
estimatedRowHeight
特点的默认值 - 指定TableView的
rowHeight
特点为UITableViewAutomaticDimension
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44;
除了进步cell
高度的核算效率之外,关于已经核算出的高度,咱们需求进行缓存
4. 直接设置frame
Autolayout 会比直接设置 frame
耗费更多的 CPU 资源
5. 图片尺度适宜
图片的 size
最好刚好跟 UIImageView
的 size
保持一致
图片经过contentMode
处理显现,对tableview
翻滚速度同样会形成影响
从网络下载图片后先根据需求显现的图片巨细切/压缩成适宜巨细的图,每次只显现处理过巨细的图片,当查看大图时在显现大图。
服务器直接回来预处理好的小图和大图以及对应的尺度最好
/// 根据特定的区域对图片进行裁剪
+ (UIImage*)kj_cutImageWithImage:(UIImage*)image Frame:(CGRect)cropRect{
return ({
CGImageRef tmp = CGImageCreateWithImageInRect([image CGImage], cropRect);
UIImage *newImage = [UIImage imageWithCGImage:tmp scale:image.scale orientation:image.imageOrientation];
CGImageRelease(tmp);
newImage;
});
}
6. 操控最大并发数量
操控一下线程的最大并发数量,当下载线程数超越2时,会显著影响主线程的功用。能够用一个NSOperationQueue
来保护下载恳求,并设置其最大线程数maxConcurrentOperationCount
。
当然在不需求呼应用户恳求时,也能够增加下载线程数来加速下载速度:
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
if (!decelerate) self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
self.queue.maxConcurrentOperationCount = 2;
}
7. 子线程处理
尽量把耗时的操作放到子线程
- 文本处理(尺度核算、制作)
- 图片处理(解码、制作)
8. 异步制作
异步制作,便是异步在画布上制作内容,将杂乱的制作进程放到后台线程中履行,然后在主线程显现。
// 异步制作,切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
UIGraphicsBeginImageContextWithOptions(size, NO, scale);
CGContextRef context = UIGraphicsGetCurrentContext();
// TODO:draw in context...
CGImageRef imgRef = CGBitmapContextCreateImage(context);
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = imgRef;
});
});
(二)GPU
1. 防止短时间内大量显现图片
尽可能将多张图片组成一张进行显现。
2. 操控尺度
GPU能处理的最大纹路尺度是4096×4096,超越这个尺度就会占用CPU资源进行处理,所以纹路尽量不要超越这个尺度。
3. 削减图层混合操作
当多个视图叠加,放在上面的视图是半通明的,那么这个时分GPU就要进行混合,把通明的颜色加上放在下面的视图的颜色混合之后得出一个颜色再显现在屏幕上,这一步是耗费GPU资源
-
UIView
的backgroundColor
不要设置为clearColor
,最好设置和superView
的backgroundColor
颜色一样 - 图片防止运用带
alpha
通道的图片
4. 通明处理
削减通明的视图,不通明的就设置opaque = YES
5. 防止离屏烘托
离屏烘托便是在当时屏幕缓冲区以外,新拓荒一个缓冲区进行操作。离屏烘托的整个进程,需求屡次切换上下文环境,先是从当时屏幕切换到离屏;比及离屏烘托结束以后,将离屏缓冲区的烘托成果显现到屏幕上,又需求将上下文环境从离屏切换到当时屏幕。
(1)下面的状况或操作会引发离屏烘托
- 光栅化,
layer.shouldRasterize = YES
- 遮罩,
layer.mask
- 圆角,一同设置
layer.masksToBounds = YES
和layer.cornerRadius > 0
- 暗影,
layer.shadow
-
layer.allowsGroupOpacity = YES
和layer.opacity != 1
- 重写
drawRect
方法
(2)圆角优化
这儿首要其实便是处理一同设置layer.masksToBounds = YES
和 layer.cornerRadius > 0
就会产生的离屏烘托。其实咱们在运用常规视图切圆角时,能够只运用view.layer.cornerRadius = 3.0
,这时是不会产生离屏烘托。可是UIImageView
有点特别,切圆角时有必要上面2句一同设置,则会产生离屏烘托,所以咱们能够考虑经过 CoreGraphics
制作裁剪圆角,或许叫美工提供圆角图片。
- (UIImage *)billy_ellipseImage {
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextAddEllipseInRect(ctx, rect);
CGContextClip(ctx);
[self drawInRect:rect];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
此外,还能够经过贝塞尔曲线画圆角:
- (void)clipCornerWithImageView:(UIImageView *)originView
andTopLeft:(BOOL)topLeft
andTopRight:(BOOL)topRight
andBottomLeft:(BOOL)bottomLeft
andBottomRight:(BOOL)bottomRight
cornerRadius:(CGFloat)radius
{
CGRect rect = originView.bounds;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
// 创立遮罩层
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = rect;
maskLayer.path = maskPath.CGPath; // 轨道
originView.layer.mask = maskLayer;
}
- (void)clipCornerWithImageView:(UIImageView *)originView
cornerRadius:(CGFloat)radius {
CGRect rect = originView.bounds;
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
// 创立遮罩层
CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
maskLayer.frame = rect;
maskLayer.path = maskPath.CGPath; // 轨道
originView.layer.mask = maskLayer;
}
这样还能够操控特定角是否设置圆角。这种状况有个坏处,便是切割视点有限,所以实现大视点圆角只能采取自己画线的方法来操作。
(3)暗影优化
关于shadow,假如图层是个简略的几何图形或许圆角图形,咱们能够经过设置shadowPath来优化功用,能大幅进步功用。
imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
(4)强制敞开光栅化
当图像混合了多个图层,每次移动时,每一帧都要从头组成这些图层,非常耗费功用,这时就能够挑选强制敞开光栅化layer.shouldRasterize = YES
。
当咱们敞开光栅化后,会在初次产生一个位图缓存,当再次运用时分就会复用这个缓存,可是假如图层产生改变的时分就会从头产生位图缓存。
所以这个功用一般不能用于UITableViewCell中,复用反而降低了功用。最好用于图层较多的静态内容的图形。
(5)优化建议
- 运用中心通明图片蒙上去到达圆角作用
- 运用ShadowPath指定layer暗影作用路径
- 运用异步进行layer烘托
- 将UITableViewCell及其子视图的opaque特点设为YES,削减杂乱图层组成
- 尽量运用不包括通明alpha通道的图片资源
- 尽量设置layer的巨细值为整形值
- 背景色的alpha值应该为1,例如不要运用clearColor
- 直接让美工把图片切成圆角进行显现,这是效率最高的一种方案
- 许多状况下用户上传图片进行显现,能够让服务端处理圆角
加载图片的特别需求
关于没有大型项目经验的我,很难触碰到设备的功用瓶颈,可是未来接触的项目里需求处理的数据会有许多,可能会有各种特别的需求,比方要求实现:
- 要求
tableView
翻滚的时分,翻滚到哪行,哪行的图片才加载并显现,翻滚进程中图片不加载显现; - 页面跳转的时分,取消当时页面的图片加载恳求;
先来看看一般的加载逻辑,放一段我之前写过的项目的代码:
如上设置,假如咱们有20行cell
,页面发动的时分,直接滑动到最底部,20个cell
都进入过了界面,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
这个方法就会被调用20次,不符合需求1。
为此,我学习了一下,学习到了两个处理方案,并自己着手实践了一下:
Runloop的小技巧
runloop
– 两种常用形式介绍: trackingMode
&& defaultRunLoopMode
- 默认状况 – defaultRunLoopMode
- 翻滚时分 – trackingMode
翻滚的时分,进入trackingMode
,这会导致defaultMode
下的使命会被暂停,中止翻滚的时分再次进入defaultMode
并持续履行defaultMode
下的使命。
代码:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
DemoModel *model = self.datas[indexPath.row];
cell.textLabel.text = model.text;
if (model.iconImage) {
cell.imageView.image = model.iconImage;
} else {
cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];
[self performSelector:@selector(billy_loadImgeWithIndexPath:)
withObject:indexPath
afterDelay:0.0
inModes:@[NSDefaultRunLoopMode]];
}
return cell;
}
//下载图片,并烘托到cell上显现
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {
DemoModel *model = self.datas[indexPath.row];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[ImageDownLoadManager runloop_loadImageWithModel:model success:^{
//主线程改写UI
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = model.iconImage;
//[cell layoutSubviews];
});
}];
}
其他方法
咱们能够手动判断UITableView
的状态,保存下载使命,然后决定履行哪些下载使命或许在适当的时机取消这些使命。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
if (!cell) {
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
}
DemoModel *model = self.datas[indexPath.row];
cell.textLabel.text = model.text;
if (model.iconImage) {
cell.imageView.image = model.iconImage;
} else {
cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];
// [self performSelector:@selector(billy_loadImgeWithIndexPath:)
// withObject:indexPath afterDelay:0.0
// inModes:@[NSDefaultRunLoopMode]];
//拖动的时分不显现
if (!tableView.dragging && !tableView.decelerating) {
//下载图片数据
[self billy_loadImgeWithIndexPath:indexPath];
}
}
return cell;
}
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {
DemoModel *model = self.datas[indexPath.row];
//保存当时正在下载的操作
ImageDownLoadManager *manager = self.imageLoadDic[indexPath];
if (!manager) {
manager = [[ImageDownLoadManager alloc] init];
//开端加载-保存到当时下载操作字典中
[self.imageLoadDic setObject:manager forKey:indexPath];
}
[manager loadImageWithModel:model success:^{
//主线程改写UI
dispatch_async(dispatch_get_main_queue(), ^{
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = model.iconImage;
[cell layoutSubviews];
});
//加载成功-从保存的当时下载操作字典中移除
[self.imageLoadDic removeObjectForKey:indexPath];
}];
}
- (void)billy_loadImage {
//拿到界面内-一切的cell的indexpath
NSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;
for (NSIndexPath *indexPath in visableCellIndexPaths) {
DemoModel *model = self.datas[indexPath.row];
if (model.iconImage) {
continue;
}
[self billy_loadImgeWithIndexPath:indexPath];
}
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
//直接中止-无动画
[self billy_loadImage];
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
[self billy_loadImage];
}
界面消失:
- (void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
NSArray *loadImageManagers = [self.imageLoadDic allValues];
//当时图片下载操作悉数取消
[loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];
}
下面附上我demo的地址:github.com/BillyMiracl…。欢迎大家下载一同学习。
先写这么多吧,学习的道路还长着呢。。。