卡顿检测与渲染优化
问题
什么是离屏渲染?如何检测和优化卡顿?
答案
掉帧原理
屏幕刷新率 60Hz → 每帧 16.67ms。如果 CPU + GPU 处理超过 16.67ms → 掉帧。
离屏渲染
GPU 无法直接渲染到帧缓冲区,需要额外开辟缓冲区 → 上下文切换开销大。
触发离屏渲染的操作:
| 操作 | 是否离屏 | 优化方案 |
|---|---|---|
cornerRadius + masksToBounds | ✅ | 用贝塞尔路径切圆角 |
shadow(无 shadowPath) | ✅ | 设置 shadowPath |
mask | ✅ | 避免或用图片替代 |
group opacity | ✅ | 设为 false |
shouldRasterize | ✅(首次) | 用于静态内容缓存 |
圆角优化
// ❌ 触发离屏渲染
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
// ✅ 方案1:用 UIBezierPath 切圆角(CPU 绘制)
let path = UIBezierPath(roundedRect: imageView.bounds, cornerRadius: 10)
let mask = CAShapeLayer()
mask.path = path.cgPath
imageView.layer.mask = mask
// ✅ 方案2:CoreGraphics 异步绘制圆角图片
func roundedImage(_ image: UIImage, radius: CGFloat) -> UIImage {
UIGraphicsBeginImageContextWithOptions(image.size, false, 0)
let rect = CGRect(origin: .zero, size: image.size)
UIBezierPath(roundedRect: rect, cornerRadius: radius).addClip()
image.draw(in: rect)
let result = UIGraphicsGetImageFromCurrentImageContext()!
UIGraphicsEndImageContext()
return result
}
卡顿检测
// RunLoop 监控方案
func startMonitor() {
let observer = CFRunLoopObserverCreateWithHandler(
kCFAllocatorDefault,
CFRunLoopActivity.allActivities.rawValue,
true, 0
) { _, activity in
// 记录 beforeWaiting → afterWaiting 的时间差
// 超过阈值(如 50ms)则上报卡顿堆栈
}
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, .commonModes)
}
常见面试问题
Q1: 如何用 Instruments 定位卡顿?
答案:使用 Core Animation 工具,开启 "Color Offscreen-Rendered Yellow"(离屏渲染区域标黄)。使用 Time Profiler 查看主线程耗时函数。帧率低于 60fps 的位置就是卡顿点。
Q2: shouldRasterize 什么时候用?
答案:适合静态内容(如不变的阴影+圆角)。开启后 GPU 首次离屏渲染后缓存为位图,后续帧直接使用缓存。动态变化的内容不要开启(缓存频繁失效反而更慢)。