绘图
概述
项目中有一个画板的功能,当时做的过程中遇到了内存增加的问题,最后通过修改绘图的方式解决了问题,在此记录一下解决问题的过程和带来的思考。
绘图实现的方式
绘图采用的是UIBezierPath + drawRect 的方法。具体操作是创建贝塞尔曲线对象在touchesBegan中获取曲线对象的起点,在touchesMoved中添加线到另一个点,然后调用setNeedsDisplay方法,该方法会自动调用drawRect方法进行绘图。详细代码如下
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = touches.anyObject;
CGPoint movePoint = [touch locationInView:touch.view];
self.path = [DrawBezierPath bezierPath];
[self.path moveToPoint:movePoint];
[self.pathArr addObject:self.path];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = touches.anyObject;
CGPoint addPoint = [touch locationInView:touch.view];
[self.path addLineToPoint:addPoint];
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect{
for (DrawBezierPath *path in self.pathArr) {
[path.pathColor setStroke];
[path strokeWithBlendMode:kCGBlendModeNormal alpha:1.0];
[path stroke];
}
}
观察代码可知道for循环遍历执行了多次,setNeedsDisplay一执行就会调用drawRect方法。这样每次画新的图的时候都要把之前所有的图在重绘一次,你画的越多,重绘的就越多。这应该就是内存增加的原因。
为什么要调用setNeedsDisplay?
在自定义的View上进行划线时调用setNeedsDisplay方法会让系统调用drawRect方法来绘图
为什么调用drawRect会增加内存?
屏幕上显示的内容基本上都是UIView,而UIView之所以能显示在屏幕上,是因为它的内部有一个CALayer图层。当UIView显示在屏幕上时会调用drawRect方法进行绘图,并且内容绘制在自己的图层上,绘图完成后系统会将图层拷贝到屏幕上,这就完成了UIView的显示。也就是说CALayer让UIView具有了显示功能,而CALayer内部有一个 contents 属性(寄宿图),在给这个属性赋值的时候并不会增加额外的内存,但是一旦实现了drawRect方法,图层就会创建一个上下文,这个上下文的内存计算公式为:图层高(分辨率) * 图层宽 * 4个字节 ,图层每次重绘的时候都需要重新抹掉内存然后重新分配。这就是导致内存增加的原因。
使用CAShapeLayer+UIBezierPath的方式绘图
CAShapeLayer 是什么
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。简单点就是UIBezierPath用来指定绘制图形路径,而CAShapeLayer就是根据路径来绘图。
CAShapeLayer 的优势
CAShapeLayer属于QuartzCore框架,drawRect属于Core Graphics框架,绘制图形时CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存。- 渲染快速,
CAShapeLayer使用了硬件加速。
CAShapeLayer 如何使用
因为都使用了贝塞尔曲线,所以和drawRect有点类似,不同的是不需要调用方法,只要把CGPath 赋给CAShapeLayer 的 path 就行了。具体代码为
- (instancetype)initWithFrame:(CGRect)frame{
if (self = [super initWithFrame:frame]) {
[self.layer addSublayer:self.shapeLayer];
}
return self;
}
- (CAShapeLayer *)shapeLayer{
if (!_shapeLayer) {
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.frame = self.bounds;
_shapeLayer.lineWidth = 2.f;
_shapeLayer.strokeColor = [UIColor redColor].CGColor;
_shapeLayer.fillColor = [UIColor clearColor].CGColor;
}
return _shapeLayer;
}
- (UIBezierPath *)shapePath{
if (!_shapePath) {
_shapePath = [UIBezierPath bezierPath];
}
return _shapePath;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
[self.shapePath moveToPoint:point];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint currentPoint = [touch locationInView:self];
[self.shapePath addLineToPoint:currentPoint];
self.shapeLayer.path = self.shapePath.CGPath;
}
总结
绘图有两种处理的方式:CPU(中央处理器)和GPU(图形处理器)。对于图像处理,通常用硬件会更快,因为GPU使用图像对高度并行浮点运算做了优化。什么专业的人做什么专业的事,这样效率更高,CAShapeLayer就是如此。