iOS 绘制原理 + 离屏渲染本质 + 列表卡顿掉帧优化 深度全解

3 阅读6分钟

一、iOS 屏幕绘制完整底层原理(必懂)

1. 硬件基础:屏幕刷新机制

屏幕固定60FPS刷新,每帧间隔:1 帧 = 16.67ms只要一帧绘制耗时超过 16.67ms,直接掉帧、卡顿

2. UI 绘制流水线(核心四步)

App 到屏幕显示经历四层:CPU 计算 → 提交图层 → GPU 渲染 → 屏幕显示

  1. CPU 阶段
  • 布局计算:AutoLayout、frame 计算
  • 文本绘制、图片解码、视图合成
  • 裁剪、阴影、遮罩逻辑计算
  • 打包成Layer 树,通过 Render Tree 提交给 GPU
  1. GPU 阶段
  • 接收 Layer 数据
  • 纹理渲染、光栅化、图层混合
  • 像素填充、合成最终帧缓存
  1. 垂直同步 VSync系统按 60Hz 节拍送帧到屏幕,CPU/GPU 耗时跟不上节拍就丢帧

3. CALayer 与 UIView 本质关系

  • UIView = 事件处理 + 布局管理
  • CALayer = 真正负责绘制、存储位图
  • 所有 UI 最终都是 CALayer 位图合成
  • UIView 只是对 Layer 的封装,触摸、布局交给 View,绘制交给 Layer。

4. 绘制时机 & RunLoop 联动

RunLoop 休眠前触发布局、绘制、刷新三步:

  1. layoutSubviews 布局(重新计算 frame)
  2. drawRect: 绘制自定义内容
  3. commitTransaction 提交 Layer 树给 GPU

所有卡顿根源:这三步耗时超标、图层过多、离屏渲染泛滥。


二、离屏渲染 Off-Screen Rendering 深度解析

1. 什么是离屏渲染?

正常渲染:GPU 在当前屏幕帧缓冲区直接逐图层叠加合成,一遍完成。

离屏渲染:GPU 先开辟一块额外临时缓冲区,把当前图层及所有子图层先渲染到临时缓冲区,再把结果合并拷贝到屏幕缓冲区。

多了一次:创建缓冲区→渲染→拷贝合并,开销暴增,极易超时掉帧。

2. 为什么会产生离屏渲染?

只要 Layer 无法直接在屏幕缓冲区实时混合,就必须离屏:

强制触发离屏渲染的属性(高频)

  1. cornerRadius + masksToBounds / clipsToBounds
  2. layer.shadowOpacityshadowOffsetshadowRadius 阴影
  3. layer.mask 遮罩图层
  4. layer.shouldRasterize = YES 光栅化
  5. 渐变图层 CAGradientLayer
  6. 复杂 drawRect: 自定义绘制大量内容
  7. 透明图层叠加过多、混合模式复杂

3. 离屏渲染为什么会卡?

  1. 额外开辟离屏缓冲区,占用显存
  2. 多一轮完整渲染 + 拷贝流程,CPU/GPU 双耗时
  3. 列表 Cell 复用,每个 Cell 都触发离屏,瞬间叠加几十上百个,直接超 16.67ms
  4. 光栅化不当会导致缓存失效、重复离屏

4. 光栅 ize 原理与正确用法

shouldRasterize = YES作用:把当前 Layer 渲染成一张位图缓存,后续直接复用位图,不用重复渲染。

适用场景:静态不变的复杂视图禁忌:列表动态 Cell、会变化的视图(复用后缓存错乱、还要重新渲染,反而更卡)

rasterize 附加配置:

objc

layer.shouldRasterize = YES;
layer.rasterizationScale = [UIScreen mainScreen].scale;

不设 scale 会模糊。


三、UITableView / UICollectionView 列表卡顿掉帧 根因归类

所有列表卡顿,只分 3 大类:

1. CPU 耗时超标

  • Cell 内部 AutoLayout 约束过多、嵌套过深
  • layoutSubviews 重复计算、频繁触发
  • 主线程做图片解码、网络请求、数据解析、复杂计算
  • 大量文本计算、富文本渲染耗时
  • 主线程读写沙盒、数据库、大量日志

2. GPU 耗时超标

  • 大量 Cell 同时离屏渲染(圆角 + 阴影 + 裁剪泛滥)
  • 图层层级过多,透明图层像素混合开销大
  • 大图未裁剪、未降分辨率,GPU 纹理过大
  • Cell 复用混乱,不断重新创建 Layer

3. 复用机制滥用 & 不合理刷新

  • 不用复用,每次 alloc init Cell
  • 无脑 reloadData 整表刷新,不用局部刷新
  • 数据源多线程竞争,刷新时机错乱、重复绘制
  • 滑动时频繁刷新 UI、定时器回调抢占主线程

四、深度优化全套方案(可直接落地)

(一)CPU 优化

  1. 坚决把耗时任务丢子线程图片解码、模型解析、数据排序、富文本预处理、本地 IO 全部异步,主线程只赋值、刷 UI。
  2. 减少 AutoLayout 开销
  • 减少约束嵌套层级
  • 固定宽高的视图直接用 frame,不用约束
  • 避免在 layoutSubviews 里写业务逻辑、重复布局
  1. 预计算 Cell 高度不要滑动时实时计算高度,提前异步算好缓存,列表直接读取,杜绝滑动实时计算卡顿。
  2. 减少主线程事务滑动过程中暂停:弹窗、轮播、定时器、埋点、日志上报,滑动结束再执行。

(二)GPU & 离屏渲染根治优化

  1. 圆角优化:放弃 clipsToBounds+cornerRadius 组合❌ 错误:

objc

view.layer.cornerRadius = 10;
view.clipsToBounds = YES; // 强制离屏渲染

✅ 最优方案:

  • 用裁切图片、遮罩图片替代
  • UIBezierPath 绘制圆角静态位图
  • 第三方圆角控件预先绘制,不实时裁剪
  1. 阴影优化不要整层加 shadow,改用:
  • 自定义背景图自带阴影
  • 用两层视图模拟阴影,避免系统 shadow 离屏
  1. 减少图层数量
  • 尽量合并多余 View,减少嵌套
  • 能用一张图片搞定,不拆多个 View + Layer
  • 减少透明视图,不透明视图设置 opaque = YES 提升混合效率
  1. 合理使用光栅化只给静态固定的复杂视图开 shouldRasterize列表动态 Cell 禁止随意开

(三)列表复用 & 刷新优化

  1. 严格复用 Cell注册 cell + dequeue 复用,禁止滑动中频繁创建新 cell。
  2. 局部刷新代替 reloadData增删 Cell 用:insertRows / deleteRows / reloadRows不整表刷新,减少重新布局和绘制。
  3. 数据源线程安全子线程只拉数据、解析,所有修改数据源、刷新 UI 统一主线程串行,解决资源竞争导致的卡顿和闪退。
  4. 图片优化
  • 列表图片按 Cell 尺寸缩略裁剪,不加载原图
  • 异步解码、缓存位图,避免滑动时实时解码
  • 用 WebP/HEIC 压缩格式,减小纹理内存

(四)检测工具定位卡顿瓶颈

  1. Xcode FPS 标尺:看帧率是否稳定 60
  2. Core Animation 工具
  • 查看 Offscreen Renderer 黄色标记:越多越卡
  • 查看图层数量、混合图层、光栅化标记
  1. Time Profiler:定位主线程耗时函数,揪出卡顿代码
  2. GPU Frame Capture:看 GPU 渲染耗时、离屏次数

五、一句话极简总结

  1. iOS 绘制是 CPU 布局计算 → GPU 图层合成,一帧超过 16.67ms 必掉帧;
  2. 离屏渲染是 GPU 额外开销,圆角 + 阴影 + 裁剪是列表卡顿头号元凶;
  3. 优化核心:减负主线程、消灭不必要离屏、精简图层、合理复用、局部刷新、异步预处理