iOS 性能优化 系列 04|图片加载与缓存,为什么总是性能问题高发区
图片问题真正麻烦的不是“要不要缓存”,而是下载、解码、缩放、内存占用和显示时机全都缠在一起
图片几乎是 iOS 项目里最容易反复出性能问题的地方。
你会在各种场景里遇到它:
- 首页卡顿
- 列表滚动掉帧
- 内存飙升
- 首屏加载慢
- 滚动时图片一闪一闪
- 同样一页内容,为什么图片一多就明显更慢
很多人一开始会把图片问题简化成一句话:
那就加缓存。
缓存当然重要,但如果只把图片优化理解成“有没有缓存”,通常离真实问题还差很远。
因为图片链路真正复杂的地方在于,它把很多成本叠在了一起:
- 下载
- 解码
- 缩放
- 渲染
- 内存持有
- 生命周期管理
所以图片问题几乎永远不是单点问题,而是一个系统问题。
一、为什么图片比普通数据更容易把问题放大
因为图片天生具备几个让性能变脆弱的特点:
- 资源体积通常更大
- 展示前往往还要解码
- 不同尺寸设备和场景会触发缩放
- 列表中会高频出现
- 很容易被缓存并长期占内存
也就是说,图片不是“多一个字段”,而是一类对 CPU、内存、带宽、渲染都同时施加压力的资源。
这就是为什么图片一旦处理不好,经常不会只表现成一个问题,而是多个问题一起出现:
- 滚动卡
- 内存高
- 首屏慢
二、下载不是唯一成本,很多团队真正低估的是解码时机
很多人一提图片加载,注意力都放在下载上:
- 图片大不大
- 网络慢不慢
- 缓存命中没有
但真实项目里,下载往往不是唯一成本,甚至经常不是滚动卡顿的核心成本。
真正容易在关键时刻拖慢体验的,常常是:
- 图片解码
- 显示前尺寸处理
- 滚动时主线程上发生的图像准备工作
也就是说,一张图片“下载完了”不等于“已经适合显示”。
如果你把很多图像处理工作压到显示瞬间,页面就很容易在最敏感的时候掉帧。
三、缓存也不是越多越好,缓存策略本身可能制造新问题
缓存是必要的,但缓存从来不是无成本的。
如果缓存策略不清楚,常见后果包括:
- 明明命中缓存了,内存却越来越高
- 磁盘缓存太激进,清理策略不合理
- 同一张图以多个尺寸反复缓存
- 列表快速切换时缓存命中看似不错,但页面仍然卡
这说明缓存真正要解决的不只是“少下一次”,还包括:
- 缓存什么尺寸
- 保留多久
- 什么时候该淘汰
- 哪些资源适合内存缓存,哪些只适合磁盘缓存
所以图片缓存从来不是一句“加缓存”就能讲清楚的,它本质上是一套资源生命周期策略。
四、列表里的图片为什么特别容易成为卡顿源头
因为列表场景天然满足几个高风险条件:
- 同屏图片数量多
- 滚动频率高
- 复用和回收频繁发生
- 用户对掉帧非常敏感
如果在这个场景里你还同时做这些事:
- 图片解码
- 尺寸计算
- 圆角裁剪
- 富文本叠层
那主线程就很容易在滚动中被压垮。
所以列表图片优化最关键的一条,不是“有没有缓存”,而是:
这些工作是不是发生在滚动关键路径上。
如果发生在关键路径上,再小的额外成本,放到高频滚动里都会被放大。
五、一个很常见的误区:把所有图片问题都归结成网络问题
真实项目里,经常会有人说:
- 图片多,所以慢
- 图片慢,所以是网络问题
这条推理经常不完整。
因为很多图片问题即使在强网、缓存命中情况下仍然存在,原因就在于:
- 图片已经拿到了,但没被提前解码
- 图片尺寸不合适,显示前又做了额外处理
- 列表滚动时状态变化过多,引发大量重绘
所以图片问题里,“资源获取”只是链路的一部分。
真正的难点在于:这条链路的每一步是否都发生在合理时机。
六、优化图片链路时,我最常问的几个问题
如果今天我要排一个图片相关性能问题,我通常会先问:
- 问题是首屏慢、列表卡,还是内存高。
- 图片是不是在显示瞬间才开始承担高成本处理。
- 当前加载的是不是合适尺寸,而不是原图硬缩。
- 缓存命中了什么,命中的到底是原图、处理后图,还是根本没命中关键尺寸。
- 图片链路是不是把太多工作压在了主线程。
这几个问题比“换个库试试”更有价值,因为它们直接逼近了真实成本。
七、结论:图片问题高发,不是因为图片特殊,而是因为它天然跨越了多种资源维度
如果只用一句话总结,我会说:
图片加载与缓存之所以总是性能问题高发区,不是因为“图片很难”,而是因为它同时牵涉带宽、CPU、内存、渲染时机和生命周期管理,一旦任意一环设计不合理,问题就会被迅速放大。
所以图片优化真正要做的,不只是缓存,而是把:
- 下载
- 解码
- 缩放
- 缓存
- 显示时机
这整条链路放在一起看。