在 iOS 开发中,卡顿(掉帧,通常指无法稳定维持 60 FPS)主要发生在主线程(UI 线程)被阻塞,导致系统无法在 16.67 毫秒(1/60 秒)内完成一帧的布局、绘制和提交工作。以下是一些最容易引发卡顿的常见情况:
1.主线程执行耗时操作
(1)网络请求同步处理:在主线程上执行同步网络请求(即使时间很短)会完全阻塞UI。
(2)复杂/大量数据处理:在主线程上进行大量数据的解析(如 JSON/XML)、计算(如 复杂算法、图像处理)、排序、过滤、数据库的读写(尤其是大查询)。
(3)文件 I/O操作:在主线程上读写大文件或执行大量文件操作。
(4)不合理的循环:在主线上执行包含大量迭代的循环,尤其是在循环体内执行了耗时的操作
2.复杂与布局计算过重的视图层级
(1)视图嵌套复杂:过于复杂的视图层级结构(尤其是大量不必要的嵌套 ‘UIView’或 ‘UIStackView’等)会显著增加系统遍历视图树和计算布局的开销。
(2)Auto Layout 约束:大量复杂的约束关系、歧义或冲突的约束、在滑动过程中频繁修改约束(导致频繁调用 ‘layoutSubViews’,而且也会伴随大量计算)
(3)手动布局计算过重:在 ‘layoutSubViews’或 ‘drawRect:’中执行过于复杂的计算逻辑。
(4)频繁计算视图的尺寸:在 ‘tableView(:heightForRowAt:)’或 'collectionView(:layout:sizeForItemAt:)'等代理方法中进行复杂的、未优化的尺寸计算(尤其是未利用缓存)。
3.图像处理与绘制
(1)加载/解码大图未优化:在主线程加载或解码分辨率远大于视图显示尺寸的大图(如 使用‘UIImage(named:)‘ / ’UIImage(contentsOfFile:)‘等阻塞式解码)。未使用异步加载或后台解码(如’UIImage‘的 ‘preparingForDisply’方法)。
(2)频繁创建/销毁 :特别是在滑动操作中,重复加载图片,未利用内存或磁盘缓存(如 SDWebImage,KingFisher等)。
(3)实现复杂的 ‘drawRect:’方法:在 ‘drawRect:’方法中使用 Core Graphics进行非常复杂的自定义绘制操作。
(4)滥用 ‘cornerRadius’ + ‘masksToBounds’:同时设置 layer.cornerRadius 和 layer.masksToBounds = YES 会强制触发离屏渲染,对性能影响很大,尤其是在大列表或者大量视图上使用。应优先考虑使用处理后的图片(切圆角)或 CAShapeLayer mask。
4.离屏渲染
(1)触发条件:‘cornerRadius’ + ‘masksToBounds’,‘layer.shadow*’,'layer.shouldRasterize = YES'(光栅化),'layer.mask','layer.allowsGroupOpacity = YES' + 非不透明背景等。
(2)影响:离屏渲染需要 GPU 在绘制当前帧前,先将图层内容渲染到一个临时缓冲区,再进行合成。这个额外的通道切换和内存搬运非常消耗 GPU 资源,当过量消耗时,会导致 GPU 负载过高,帧提交只能延迟。
5.对象创建和销毁
(1)频繁创建/销毁重量级对象:在循环或频繁调用饿方法中大量创建临时 UIView、 UIImage、 NSDateFormatter 等重量级对象。创建和销毁本身就有开销,还会增加 ARC 管理和内存回收的压力。
(2)未使用 ‘@autoreleasepool’:在包含大量临时对象创建的循环中,如果不及时释放内存,可能导致内存峰值过高或触发 GC 停顿。
6.动画与交互处理不当
(1)主线程动画阻塞:在主线程执行本应交给 Core Animation 的动画逻辑。
(2)过度使用 setNeedsDisplay/setNeedsLayout:不必要或过于频繁地调用这些方法,导致系统在不需要的时候也进行昂贵的重绘或重新布局。
7.其他
(1)文本计算与渲染:复杂富文本的布局和渲染可能很慢。
(2)过度绘制:视图叠加导致 GPU 在同一个像素区域绘制多次。
(3)隐式动画:修改CALayer的可动画属性,会触发默认的0.25秒隐式动画。如果在主线程大量修改且不希望有动画,应该使用 CATransaction包裹并禁用动画。
(4)系统方法滥用:如过度调用 【UIView snapshotViewAfterScreenUpdates:】或 UIGraphicsBeginImageContextWithOptions等可能阻塞主线程的API。
如果想解决以上问题,首先就是如何去确认问题。
1.Instruments工具
(1)Time Profiler:定位主线程耗时点。
(2)Core Animation:检测帧率,识别离屏渲染(黄色警告)、光栅化(蓝色警告)、绘制瓶颈(红色/橙色条)、合成混合问题(绿色警告)。
(3)Leaks & Allocations:查找内存泄露、内存峰值和频繁创建/销毁的对象。
(4)Energy Log:高能耗有时伴随卡顿(如后台持续计算)。
2.Xcode 调试器
(1)主线程挂起检查:暂停应用,看主线程的调用栈卡在哪里。
(2)视图调节器:检查视图层级复杂度。
解决方案
(1)严格遵循:所有耗时操作移除主线程。
(2)简化视图层级:使用 Debug View Hierarcy 工具分析并移除不必要的视图和嵌套。
(3)优化 Auto Layout: 减少约束数量/复杂度,避免循环依赖,善用 UILayoutGuide,在滚动时批量修改约束。
(4)缓存: 缓存视图尺寸、计算结果、图片、格式化对象(如 DateFormatter)。
(5)图片优化: 异步加载+解码,使用正确尺寸,利用缓存库,慎用圆角阴影(优先预处理图片)。
(6)减少离屏渲染: 优先使用预合成图片、CAShapeLayer 绘制圆角/阴影、谨慎使用 shouldRasterize(仅用于静态或低频变化内容)。
(7)惰性加载与对象复用: 延迟创建对象,复用 Cells、Views、Formatter 等。
(8)合理使用 @autoreleasepool: 在创建大量临时对象的循环中使用。
(9)优化文本处理: 预计算富文本尺寸,复用文本布局对象。
(10)性能敏感操作避让滚动: 将非必要的重布局、重绘、数据加载等操作延迟到滚动停止后进行。