前言
在上篇iOS截图引起的思考留下了一些疑问,会在这篇全部解开。
在上一篇结尾的时候,我提出了:
即使是相同的path,drawRect也是有可能会比CAShapeLayer要快的。
正文
先来看两段贝塞尔画出来的路径:
- 这一段路径是在悦跑圈实际遇到的轨迹情况
func setupAPath() -> UIBezierPath { let bezier = UIBezierPath() //https://github.com/Fidetro/drawPath/blob/master/DrawPath/test.plist let plistPath = Bundle.main.path(forResource: "test", ofType: "plist")! let points = (NSArray.init(contentsOfFile: plistPath) as! [String]).map{CGPointFromString($0)} for (index,point) in points.enumerated() { if index == 0 { bezier.move(to: point) }else{ bezier.addLine(to: point) } } return bezier }
- 这一段路径是两点之间来回绘制5000次
func setupBPath() -> UIBezierPath { let bezier = UIBezierPath() for index in 0..<10000 { if index == 0 { bezier.move(to: CGPoint.init(x: 0, y: 0)) }else{ let point = index%2==0 ? CGPoint.init(x: 100, y: 100) : CGPoint.init(x: 0, y: 0) bezier.addLine(to: point) } } return bezier }
这两段路径分别使用CAShapeLayer
和CGContext
绘制,下面是粗略统计的耗时时间:
耗时 | APath | BPath |
---|---|---|
CAShapeLayer | 3~4s | 5~6min |
drawRect | 4~5min | 5~6s |
这里有一点要注意的是,CGContext
会消耗CPU和内存,使用Xcode或者Instruments可以发现,在绘制APath的时候CPU使用率基本在90%以上,CAShapeLayer使用GPU绘制,但仍然会造成卡顿。(还没找到监测GPU使用的方法,如果有可以评论告诉我。
要了解造成这两者之间差异的原因,就得知道CAShapeLayer
和CGContext
的由来。
在上一篇中已经简单介绍过,从官方文档中可以得知,CAShapeLayer是基于Core Animation上封装的类,用于绘制抗锯齿形状图层。而CGContext
是使用Core Graphics
。光知道他们调用是底层是什么,并不能够解决现在的问题,还需要更深入去了解Core Animation
和Core Graphics
。
Core Animation
Core Animation
是Metal
和Core Graphics
更上层的框架。使用Core Animation
绘制,只需要设置一些参数(例如起点和终点),然后Core Animation
会将绘制工作交给GPU去渲染。
CAShapeLayer
同时关于CAShapeLayer
,苹果更建议我们在一些简单的路径的时候去使用。
CAShapeLayer
在合成的时候根据提供的路径去渲染位图,优势是尽可能的用更好的分辨率去绘制路径,但是这种优势是要以额外的渲染时间作为代价,如果提供的路径非常复杂,那么可能会导致栅格化的代价十分昂贵。同时如果图层大小不断变化,导致经常需要重新绘制,那么绘制花费的时间最终可能会变成性能瓶颈。
Core Graphics
Core Graphics也叫做Quartz 2D,是二维绘图引擎,Quartz 2D提供低级、轻量级2D渲染,很方便实现透明层,基于路径的绘图,离屏渲染,高级颜色管理,消除锯齿渲染以及PDF创建,显示和解析。
Core Graphics
建议我们在遇到以下情况的时候使用CGLayer
去绘制。
- 重复使用高质量的离屏渲染。例如你在构建一个需要复用的场景时候,将背景绘制在图层上,然后在需要的时候绘制图层。
- 重复绘制。如果你打算绘制一个由多个相同的项目组成的图层,将其绘制到CGLayer上,能得到性能提升。(参考下图)
- 缓冲。虽然你可能会因此使用layer,但是Quartz Compositor并不需要缓冲,如果你必须绘制到缓冲区,请使用layer而不是context。
使用CGLayerRef
能实现最佳性能,Quartz会根据关联的图形上下文来缓存CGLayer。例与视显卡相关联的图形上下文可以缓存显卡上的图层,这会让绘制图层中的内容比从位图上下文构造的图像渲染的更快。因为,与位图图形上下文相比,Layer通常是离屏渲染的更好选择。
CGLayerRef
的使用可以参考下面这段代码,CGLayerRef在swift中对应的是CGLayer
let context = UIGraphicsGetCurrentContext()!
let layer = CGLayer.init(context, size: rect.size, auxiliaryInfo: nil)
let layerCtx = layer!.context!
layerCtx.beginPath()
layerCtx.move(to: CGPoint.init(x: 0, y: 0))
layerCtx.addLine(to: CGPoint.init(x: 100, y: 100))
layerCtx.setLineWidth(2)
layerCtx.setStrokeColor(UIColor.red.cgColor)
layerCtx.strokePath()
layerCtx.closePath()
context.draw(layer!, in: self.frame)
总结
通过两者文档的对比,似乎CGLayerRef
比CAShapLayer
要更好,但是实际上,在绘制复杂的路径的时候,个人还是更偏向使用CAShapeLayer
,大部分情况下,CPU的性能比GPU的性能更加昂贵,在两者之间选择的时候,应该根据实际要求,如果需要更高质量的图形渲染,应该选择Core Graphics
。
另外在刚开始的时候提出CAShapeLayer
绘制BPath路径的时候会更加卡顿的问题,尽管苹果推荐我们CAShapeLayer
应该去绘制简单的路径,但是苹果也在文档中给出了解决的方法:
通过将复杂的形状分解成更简单的形状可以减少绘制花费的时间,将多个CAShapeLayer
对象叠加在一起要比单独绘制一个CAShapeLayer
对象快得多。因为绘制操作是在CPU上,而合成是在GPU上。但是这部分的性能优化,需要根据实际的情况去制定基线。
参考链接
Core Animation Programming Guide
Quartz 2D Programming Guide
示例Demo
请保持转载后文章内容的完整,以及文章出处。本人保留所有版权相关权利。