一、iOS 屏幕绘制完整底层原理(必懂)
1. 硬件基础:屏幕刷新机制
屏幕固定60FPS刷新,每帧间隔:1 帧 = 16.67ms只要一帧绘制耗时超过 16.67ms,直接掉帧、卡顿。
2. UI 绘制流水线(核心四步)
App 到屏幕显示经历四层:CPU 计算 → 提交图层 → GPU 渲染 → 屏幕显示
- CPU 阶段
- 布局计算:AutoLayout、frame 计算
- 文本绘制、图片解码、视图合成
- 裁剪、阴影、遮罩逻辑计算
- 打包成Layer 树,通过 Render Tree 提交给 GPU
- GPU 阶段
- 接收 Layer 数据
- 纹理渲染、光栅化、图层混合
- 像素填充、合成最终帧缓存
- 垂直同步 VSync系统按 60Hz 节拍送帧到屏幕,CPU/GPU 耗时跟不上节拍就丢帧。
3. CALayer 与 UIView 本质关系
UIView= 事件处理 + 布局管理CALayer= 真正负责绘制、存储位图- 所有 UI 最终都是 CALayer 位图合成
- UIView 只是对 Layer 的封装,触摸、布局交给 View,绘制交给 Layer。
4. 绘制时机 & RunLoop 联动
RunLoop 休眠前触发布局、绘制、刷新三步:
layoutSubviews布局(重新计算 frame)drawRect:绘制自定义内容commitTransaction提交 Layer 树给 GPU
所有卡顿根源:这三步耗时超标、图层过多、离屏渲染泛滥。
二、离屏渲染 Off-Screen Rendering 深度解析
1. 什么是离屏渲染?
正常渲染:GPU 在当前屏幕帧缓冲区直接逐图层叠加合成,一遍完成。
离屏渲染:GPU 先开辟一块额外临时缓冲区,把当前图层及所有子图层先渲染到临时缓冲区,再把结果合并拷贝到屏幕缓冲区。
多了一次:创建缓冲区→渲染→拷贝合并,开销暴增,极易超时掉帧。
2. 为什么会产生离屏渲染?
只要 Layer 无法直接在屏幕缓冲区实时混合,就必须离屏:
强制触发离屏渲染的属性(高频)
cornerRadius+masksToBounds/clipsToBoundslayer.shadowOpacity、shadowOffset、shadowRadius阴影layer.mask遮罩图层layer.shouldRasterize = YES光栅化- 渐变图层
CAGradientLayer - 复杂
drawRect:自定义绘制大量内容 - 透明图层叠加过多、混合模式复杂
3. 离屏渲染为什么会卡?
- 额外开辟离屏缓冲区,占用显存
- 多一轮完整渲染 + 拷贝流程,CPU/GPU 双耗时
- 列表 Cell 复用,每个 Cell 都触发离屏,瞬间叠加几十上百个,直接超 16.67ms
- 光栅化不当会导致缓存失效、重复离屏
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 initCell - 无脑
reloadData整表刷新,不用局部刷新 - 数据源多线程竞争,刷新时机错乱、重复绘制
- 滑动时频繁刷新 UI、定时器回调抢占主线程
四、深度优化全套方案(可直接落地)
(一)CPU 优化
- 坚决把耗时任务丢子线程图片解码、模型解析、数据排序、富文本预处理、本地 IO 全部异步,主线程只赋值、刷 UI。
- 减少 AutoLayout 开销
- 减少约束嵌套层级
- 固定宽高的视图直接用 frame,不用约束
- 避免在
layoutSubviews里写业务逻辑、重复布局
- 预计算 Cell 高度不要滑动时实时计算高度,提前异步算好缓存,列表直接读取,杜绝滑动实时计算卡顿。
- 减少主线程事务滑动过程中暂停:弹窗、轮播、定时器、埋点、日志上报,滑动结束再执行。
(二)GPU & 离屏渲染根治优化
- 圆角优化:放弃 clipsToBounds+cornerRadius 组合❌ 错误:
objc
view.layer.cornerRadius = 10;
view.clipsToBounds = YES; // 强制离屏渲染
✅ 最优方案:
- 用裁切图片、遮罩图片替代
- 用
UIBezierPath绘制圆角静态位图 - 第三方圆角控件预先绘制,不实时裁剪
- 阴影优化不要整层加 shadow,改用:
- 自定义背景图自带阴影
- 用两层视图模拟阴影,避免系统 shadow 离屏
- 减少图层数量
- 尽量合并多余 View,减少嵌套
- 能用一张图片搞定,不拆多个 View + Layer
- 减少透明视图,不透明视图设置
opaque = YES提升混合效率
- 合理使用光栅化只给静态固定的复杂视图开
shouldRasterize,列表动态 Cell 禁止随意开。
(三)列表复用 & 刷新优化
- 严格复用 Cell注册 cell + dequeue 复用,禁止滑动中频繁创建新 cell。
- 局部刷新代替 reloadData增删 Cell 用:
insertRows/deleteRows/reloadRows不整表刷新,减少重新布局和绘制。 - 数据源线程安全子线程只拉数据、解析,所有修改数据源、刷新 UI 统一主线程串行,解决资源竞争导致的卡顿和闪退。
- 图片优化
- 列表图片按 Cell 尺寸缩略裁剪,不加载原图
- 异步解码、缓存位图,避免滑动时实时解码
- 用 WebP/HEIC 压缩格式,减小纹理内存
(四)检测工具定位卡顿瓶颈
- Xcode FPS 标尺:看帧率是否稳定 60
- Core Animation 工具
- 查看 Offscreen Renderer 黄色标记:越多越卡
- 查看图层数量、混合图层、光栅化标记
- Time Profiler:定位主线程耗时函数,揪出卡顿代码
- GPU Frame Capture:看 GPU 渲染耗时、离屏次数
五、一句话极简总结
- iOS 绘制是 CPU 布局计算 → GPU 图层合成,一帧超过 16.67ms 必掉帧;
- 离屏渲染是 GPU 额外开销,圆角 + 阴影 + 裁剪是列表卡顿头号元凶;
- 优化核心:减负主线程、消灭不必要离屏、精简图层、合理复用、局部刷新、异步预处理。