查看YYKit的微博实例,学习tableView如何优化:
一.预排版
- 获取到json——》后台线程对每个cell数据封装为一个布局对象 CellLayout,缓存到内存,以供稍后使用:
1)封装为model:WBTimeLineItem
2)计算布局:WBStatueLayout;属性里面有model、布局结果
TableView 在请求各个高度函数时,不会消耗任何多余计算量;当把 CellLayout 设置到 Cell 内部时,Cell 内部也不用再计算布局了。
- 其他
cell里面的每部分view都进行封装;并和cell放在一个类文件里面;
model里面的每个小model,也和大model放在一个类文件里面;
布局的layout文件,也是大类文件里面包含小的;
cell上面布局如下:
二.预渲染:
1.圆角头像:
YYKit的demo里面是把头像下载后在后台线程渲染为圆型后,放到ImageCache缓存中。
对于 TableView 来说,Cell 内容的离屏渲染会带来较大的 GPU 消耗。尽量避免使用 layer 的 border、corner、shadow、mask 等技术,而尽量在后台线程预先绘制好对应内容。
-方法1: controller里面削圆具体操作如下(自己写的):
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:@"11_1684_773"];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = [self getImage:image];//削圆
[self.view addSubview:imageView];
}
//削圆
-(UIImage *)getImage:(UIImage *)image{
//1、开启上下文
UIGraphicsBeginImageContextWithOptions(image.size, NO, 0);
//2、设置裁剪区域(rect:圆的x,y,直径)
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, 512, 512)];
[path addClip];
//3、绘制图片
[image drawAtPoint:CGPointZero];
//4、获取新图片
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
//5、关闭上下文
UIGraphicsEndImageContext();
//6、返回新图片
return newImage;
}
- 方法2:使用UIImage的分类操作如下(YYKit微博demo里面这么写):
.h:
@interface UIImage (TT)
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius;
@end
.m:
#import "UIImage+TT.h"
@implementation UIImage (TT)
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius {
return [self imageByRoundCornerRadius:radius borderWidth:0 borderColor:nil];
}
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor {
return [self imageByRoundCornerRadius:radius
corners:UIRectCornerAllCorners
borderWidth:borderWidth
borderColor:borderColor
borderLineJoin:kCGLineJoinMiter];
}
//把图片预渲染为圆形
- (UIImage *)imageByRoundCornerRadius:(CGFloat)radius
corners:(UIRectCorner)corners
borderWidth:(CGFloat)borderWidth
borderColor:(UIColor *)borderColor
borderLineJoin:(CGLineJoin)borderLineJoin {
if (corners != UIRectCornerAllCorners) {
UIRectCorner tmp = 0;
if (corners & UIRectCornerTopLeft) tmp |= UIRectCornerBottomLeft;
if (corners & UIRectCornerTopRight) tmp |= UIRectCornerBottomRight;
if (corners & UIRectCornerBottomLeft) tmp |= UIRectCornerTopLeft;
if (corners & UIRectCornerBottomRight) tmp |= UIRectCornerTopRight;
corners = tmp;
}
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextScaleCTM(context, 1, -1);// 缩放
CGContextTranslateCTM(context, 0, -rect.size.height);//平移
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2.0) {//削圆
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, 0, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(radius, borderWidth)];
[path closePath];
CGContextSaveGState(context);
[path addClip];
CGContextDrawImage(context, rect, self.CGImage);
CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = radius > self.scale / 2 ? radius - self.scale / 2 : 0;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, borderWidth)];
[path closePath];
path.lineWidth = borderWidth;
path.lineJoinStyle = borderLineJoin;
[borderColor setStroke];
[path stroke];
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
@end
viewcontroller里面:
- (void)viewDidLoad {
[super viewDidLoad];
UIImage *image = [UIImage imageNamed:@"11_1684_773"];
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = [image imageByRoundCornerRadius:image.size.width/2.0];
[self.view addSubview:imageView];
}
2.其他削圆方法
1.最常见的方法:
UIImageView * imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
imageView.image = [UIImage imageNamed:@"11_1684_773"];
//削圆
imageView.layer.masksToBounds = YES;
imageView.layer.cornerRadius = 50;
[self.view addSubview:imageView];
这种对图片进行削圆会产生离屏渲染。如果不是tableView上面进行削圆,削圆的视图不是很多,直接这么操作即可。
2.使用CAShapeLayer和UIBezierPath设置圆角
//1.创建削圆的曲线
UIBezierPath * maskPath = [UIBezierPath bezierPathWithOvalInRect:self.imageView.bounds];
//2.创建shaperLayer
CAShapeLayer * maskLayer = [[CAShapeLayer alloc] init];
//设置shaperLayer大小
maskLayer.frame = self.imageView.bounds;
//把曲线设置为shaperLayer的削圆曲线
maskLayer.path = maskPath.CGPath;
//3.把shaperLayer设置为imageView.layer.mask
self.imageView.layer.mask = maskLayer;
self.imageView.image = [UIImage imageNamed:@"1.png"];
使用的是设置蒙层的方法,其实也会产生离屏渲染。
异步绘制
下面这几部分还没有学会,道行太浅,需要把每个部分细致的学习后再回来慢慢咀嚼其中的好处;
demo里面把显示文本的控件上用到了异步绘制的功能;
全局并发控制
大量的任务提交到后台队列时,某些任务会因为某些原因(此处是 CGFont 锁)被锁住导致线程休眠,或者被阻塞,concurrent queue 随后会创建新的线程来执行其他任务。
这样App 在同一时刻就会存在几十个线程同时运行、创建、销毁。这些操作仍然会挤占掉主线程的 CPU 资源。
YYKit作者的 YYDispatchQueuePool,为不同优先级创建和 CPU 数量相同的 serial queue,每次从 pool 中获取 queue 时,会轮询返回其中一个 queue。
把 App 内所有异步操作,包括图像解码、对象释放、异步绘制等,都按优先级不同放入了全局的 serial queue 中执行,这样尽量避免了过多线程导致的性能问题。
更高效的异步图片加载
显示简单的单张图片时,利用 UIView.layer.contents就足够;使用 UIImageView 反而会带来额外的资源消耗;
YYKite作者实现了一个性能更高的图片加载库;在 CALayer 上添加了 setImageWithURL 等方法。
其他学习到的点:
- 有不少视觉元素并不需要触摸事件,这些元素可以用 ASDK(第三方) 的图层合成技术预先绘制为一张图
- 减少每个 Cell 内图层的数量,用 CALayer 替换掉 UIView
- 把 Cell 按类型划分,进一步减少 Cell 内不必要的视图对象和操作
后续还需要学习:
简单的 FPS 指示器:FPSLabel
在后台线程操作对图片削圆,然后放到ImageCache缓存中
使用CADisplayLink显示FPS
-为了达到最高性能,你可能需要牺牲一些开发速度,不要用 Autolayout 等技术,少用 UILabel 等文本控件,所以还需要学习 layer的使用Quartz 2D
YYKit的学习:YYLayout、YYDispatchQueuePool
、里面对图片的加载和解码的使用