简单来说:CPU 擅长处理逻辑、布局和解码,GPU 擅长渲染纹理、混合和像素填充。 平衡的目标是让两者都不成为瓶颈,且合理分工。
以下是针对大量图层绘制场景的平衡策略,按优先级排序:
1. 识别瓶颈(最重要)
不要盲目优化。使用 Xcode 工具定位瓶颈:
-
Instruments - Time Profiler:看 CPU 是否爆满。如果主线程在
layoutSubviews、drawRect、图片解码上耗时过多,瓶颈在 CPU。 -
Instruments - Core Animation:看 FPS 和 GPU 利用率。
- Debug Options 中的 Color Offscreen-Rendered(离屏渲染):黄色图层过多会增加 GPU 负担。
- Color Blended Layers(图层混合):红色区域过多会增加 GPU 像素填充率压力。
- Color Hits Green and Misses Red:检查
shouldRasterize的光栅化缓存是否有效。
2. CPU 侧优化(减轻主线程压力)
大量绘制通常导致 CPU 忙于创建视图、计算布局、解码图片。
减少视图层级
- 使用
CALayer替代UIView:如果图层不需要响应交互(点击、手势),直接用CALayer。UIView是对CALayer的封装,包含事件处理逻辑,开销更大。 - 减少子视图数量:在列表或滚动视图中,尽量复用视图(
UITableView/UICollectionView复用机制)。对于极其复杂的静态界面,考虑使用drawRect直接绘制 或使用TextKit/ Core Text 将多个控件合并为一个视图绘制。
异步化非 UI 操作
-
异步绘制:如果必须实现
drawRect,确保绘图逻辑放在子线程。可以封装一个UIView,其drawRect只是将数据传递给后台,后台生成图片后设置contents。- YYText、AsyncDisplayKit (Texture) 等框架的核心原理就是异步绘制。
-
异步图片解码:
UIImage加载后通常是未解码的。系统在渲染到 GPU 前会在主线程解码。务必使用第三方库(如 SDWebImage、Kingfisher)并在后台强制解码(将其绘制到图形上下文中)。 -
栅格化(Rasterization) :
- 对于复杂、静态、且频繁重用的图层(如列表的圆角头像阴影),设置
layer.shouldRasterize = YES。 - 注意:这会将图层渲染成位图缓存到 GPU 内存。如果图层经常变化(滚动中的 Cell),栅格化会变成性能杀手(反复创建缓存,导致 GPU 抖动)。仅用于静态且不变的视图。
- 对于复杂、静态、且频繁重用的图层(如列表的圆角头像阴影),设置
3. GPU 侧优化(减轻渲染压力)
GPU 主要负责纹理上传、顶点转换、像素填充(光栅化)。大量图层容易导致 Overdraw(过度绘制) 和 带宽瓶颈。
避免离屏渲染
离屏渲染会迫使 GPU 开辟额外缓冲区进行上下文切换,开销极大。
-
圆角:不要使用
layer.masksToBounds + cornerRadius组合(会导致离屏渲染)。- 方案:使用 带圆角的图片;或者使用 CAShapeLayer 作为遮罩(虽然也是离屏,但通常比 masksToBounds 效率高且可控);性能要求极高时,直接让设计师提供圆角素材。
-
阴影:
- 避免使用
shadowOffset+shadowOpacity组合且无shadowPath(会导致离屏渲染)。 - 方案:必须指定
layer.shadowPath,告诉 GPU 阴影的形状,避免 GPU 去计算视图的 Alpha 通道来生成阴影。
- 避免使用
-
组透明度:少用
layer.allowsGroupOpacity。
避免图层混合
GPU 混合像素时,需要将多层图层计算叠加。
- 背景色不透明:确保
UIView的opaque = YES,并且backgroundColor设置为非透明色(通常是白色)。如果视图完全覆盖其父视图,务必标记为不透明。 - 避免透明重叠:在滚动视图中,尽量减少透明图层(Alpha < 1)的重叠层数。
控制纹理大小
- 图片尺寸:不要直接显示 4096x4096 的原图然后缩放到 50x50。GPU 需要加载整个纹理到内存。必须根据显示尺寸裁剪图片。
- 格式:使用
MTLPixelFormatBGRA8Unorm或kCVPixelFormatType_32BGRA。在 iOS 上,BGRA 格式是 GPU 原生支持的,速度最快。
4. 架构层面的选择:UIKit 与 Metal
如果绘制量极大(例如绘图类 App、复杂图表、游戏):
-
普通场景(几百个图层内) :优化后的 UIKit / Core Animation 足够。
-
极端场景(数万个图层或实时粒子效果) :
- Texture (AsyncDisplayKit) :将
UIView替换为ASDisplayNode。它将视图的创建、布局、绘制全部放到后台线程,只将最终的contents提交给 GPU。这是目前 iOS 复杂界面性能优化的天花板。 - Metal / OpenGL ES:直接接管渲染管线。如果需要在同一时间绘制几千个独立的元素(如地图瓦片层、手写笔迹),使用 Metal 手动管理顶点缓冲区和片段着色器,可以绕过 UIKit 的隐式开销。
- Texture (AsyncDisplayKit) :将
5. 实践总结:平衡法则
| 场景 | 策略 | 平衡点 |
|---|---|---|
| 列表滚动卡顿 | 1. 复用机制 2. 异步绘制/解码 3. 设置 shadowPath | CPU 处理复用逻辑和布局;GPU 只负责简单纹理填充。 |
| 界面启动/切换慢 | 减少首屏视图层级;懒加载;使用 CALayer 替代 UIView | CPU 减少布局计算时间;GPU 减少首帧渲染指令数量。 |
| 动画掉帧 | 开启 shouldRasterize(仅对不变图层);避免离屏渲染;尽量使用 CATransform3D 而非修改 frame | 让 GPU 处理矩阵变换(硬件加速),CPU 不参与每一帧的坐标计算。 |
| 耗电发热 | 降低刷新率(CADisplayLink 降频);减少混合;缩小图片纹理 | GPU 负载过高是发热主因。减少像素填充率比降低 CPU 频率更有效。 |
核心原则
将 UIKit 的隐式开销显式化,把非 UI 线程的活移出主线程,把离屏渲染和混合留给 GPU 的带宽极限。