本文在 01~06 基础上,汇总 内存警告、Instruments 排查与泄漏分析、Timer 管理、野指针、音视频与图层场景 等实践要点,以及常见问题与最佳实践。建议先掌握总纲与 ARC、weak 等再阅读本文;Timer 与 NSProxy 见 06-weak与循环引用。
一、内存警告(Memory Warning)
1.1 机制
- 系统在内存紧张时向应用发送 UIApplication 内存警告(如
didReceiveMemoryWarning);若不释放非必要缓存,系统可能终止进程。
1.2 响应建议
| 做法 | 说明 |
|---|
| 释放缓存 | 图片缓存、数据缓存等可重建的,在收到警告时清理或缩小 |
| 释放不可见资源 | 非当前页的大图、大模型等可延迟重新加载的,可先释放 |
| 不阻塞主线程 | 释放与重建尽量异步,避免卡顿 |
1.3 回调示例(ViewController)
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
二、内存泄漏(Leak)排查
2.1 常见原因
- 循环引用:对象成环,引用计数永不为 0 → 用 weak 打破。
- 定时器/观察者未移除:NSTimer、KVO、Notification 等强引用 target/observer,未在 dealloc 前移除 → 及时 invalidate/removeObserver。
- Block/闭包强引用:block 强引用 self 且 self 强引用 block →
[weak self];Block 类型与 copy 语义见 10-Block内存管理。
- Category 关联对象:用
objc_setAssociatedObject 时若用 RETAIN 关联了「会强引用主对象」的对象(如 block 捕获 self),会形成循环引用;应避免或改用 weak 打破。详见 09-Category与关联对象内存管理。
2.2 Instruments(Leaks / Allocations)
- Leaks:检测进程内已无法被引用到的「泄漏」内存块。
- Allocations:查看各对象分配与存活情况,结合 Generations 或 Mark Generation 观察某操作后是否持续增长不降。
- 结合 Call Tree 与源码,定位泄漏对象与引用链。
2.3 内存泄漏的内存分析(进阶)
- 堆快照与对比:在 Allocations 中多次 Mark Generation(如进入页面前 Mark、返回后再 Mark),对比两次快照的「Persistent」对象数量与大小,找出本应释放却仍存活的对象。
- VM 区域:在 Allocations 的 Statistics 或 VM Tracker 中查看各 VM 区域(如 CG image、Image IO、IOSurface、Audio 等),定位是哪类内存持续增长(如解码图、音视频缓冲未释放)。
- 引用链分析:对疑似泄漏对象右键 Show in Memory Graph 或查看 Reference History,看清「谁在持有它」的引用链,从而找到应改为 weak 或应 invalidate/remove 的持有方。
- Malloc Stack / Call Tree:开启 Malloc Stack(Allocations 模板或 Edit Scheme → Diagnostics)可看到分配时的调用栈,便于确认泄漏对象来自哪段代码;Call Tree 的「Invert Call Tree」「Hide System」可快速聚焦业务代码。
- Leaks 与 Allocations 配合:Leaks 只报「不可达」的泄漏;很多「仍被错误持有」的对象不会报 Leak,需用 Allocations 的 Generation 对比 + 引用链分析。
2.4 Timer 管理(详细)
- NSTimer 会强引用 target,且 RunLoop 会持有 timer;若 VC 强引用 timer 且 target 是 self,则 VC → timer → self 形成循环,VC 不会 dealloc。
- 解决:① 在 dealloc 前 invalidate(若循环未破,dealloc 不会被调用,故须先破环);② 用 NSProxy 弱引用 self 作为 timer 的 target,使 timer 强引用的是 proxy 而非 VC,详见 06-weak与循环引用 中的「NSProxy 与 Weak、Timer 管理」;③ iOS 10+ 使用 block 版
scheduledTimerWithTimeInterval:repeats:block:,block 内用 weak self,timer 不直接强引用 self。
- CADisplayLink 同样强引用 target,需用相同思路(proxy 或 block 若可用)并在 dealloc 里
invalidate。
三、野指针与崩溃
3.1 成因
- 对象已 release/dealloc,仍有指针访问该内存 → 野指针;再次向该对象发消息或访问成员易 EXC_BAD_ACCESS 等崩溃。
3.2 预防
| 手段 | 说明 |
|---|
| ARC + weak | 使用 weak 时,对象释放后指针自动置 nil,发送消息无效果但不会崩溃 |
| 不重复 release(MRC) | MRC 下严格配对,避免对同一对象多次 release |
| 置空指针 | 释放后将指针置 nil(ARC 中 weak 自动完成) |
四、音视频场景内存注意
- 解码缓冲与采样缓冲:音视频解码会产生 CVPixelBuffer、CMSampleBuffer 等,若不及时释放或重复堆积,会快速推高内存;播放/渲染完或不再需要时应及时释放,避免在回调或队列中积压。
- 大文件/流:避免一次性将整段音视频读入内存;使用 AVAssetReader、流式读取 等按需加载,及时释放已解码帧或已播放的缓冲。
- 后台与生命周期:进入后台时释放非必要解码器、清空大缓冲或暂停解码,回到前台再重建,可配合 UIApplication 后台通知 与 didReceiveMemoryWarning。
- 循环引用:在 AVFoundation 回调、block 中若使用 self,需 weak self,避免 VC 或播放器持有 block 且 block 强引用 self 导致不释放。
- CVPixelBuffer / 图像缓冲:渲染或处理完及时 CVPixelBufferRelease(若自己 retain 过)或交给系统回收;避免在缓存中无上限保留未释放的 buffer。
内存极限管理(缓存上限、后台释放、按需加载、内存映射等)见 12-Option与内存优化技术 中的「内存的极限管理」一节。
五、图层处理场景内存注意
- 图片解码与尺寸:UIImage 在赋值给 UIImageView 或绘制前会解码为位图,大图会占用 宽×高×4 字节 量级内存;应对大图做降采样(如用 Image I/O 或 Core Graphics 按显示尺寸解码),或使用缩略图/裁剪,避免全尺寸解码多张大图。
- CALayer 与 backing store:图层有内容(如 contents、drawRect)时会有 backing store 占用内存;离屏渲染(圆角+裁剪、阴影、group opacity 等)会生成额外离屏缓冲,多而大时会增加内存与 GPU 压力,可适当减少离屏层或用位图缓存。
- 离屏渲染与缓存:
shouldRasterize = YES 会缓存光栅化结果,图层复杂或尺寸大时缓存会占内存;在不需要时关闭或缩小 layer bounds。
- 大图列表:列表(UITableView/UICollectionView)中大量大图时,做好 复用、按需加载 与 内存警告时释放;可配合 didReceiveMemoryWarning 清空图片缓存。
- Core Graphics / 位图:自己创建的 CGContext、CGBitmapContext 在不用时 CGContextRelease;UIGraphics 的 context 若为自己创建需对应释放。
六、最佳实践小结
| 场景 | 建议 |
|---|
| 属性默认 | 对象类型用 strong;delegate/dataSource 用 weak |
| Block | 若 block 被 self 持有且 block 内用 self,用 weak self;block 内若需保证执行期 self 存活,可再 strong 一次;Block 属性用 copy/strong,详见 [10-Block内存管理](10-主题 | 内存管理@iOS-Block内存管理.md) |
| 定时器/通知 | 在 dealloc 前 invalidate timer、removeObserver,避免强引用导致不释放 |
| 大量临时对象 | 循环内使用 @autoreleasepool 控制峰值 |
| 内存警告 | 实现 didReceiveMemoryWarning,释放可重建缓存 |
| Category 关联对象 | 用 OBJC_ASSOCIATION_RETAIN/COPY 存对象、OBJC_ASSOCIATION_COPY 存 block;避免关联会强引用主对象的对象以防循环引用,详见 [09-Category与关联对象内存管理](09-主题 | 内存管理@iOS-Category与关联对象内存管理.md) |
| 集合/对象拷贝 | 区分浅拷贝(新容器、元素共享)与深拷贝(完全独立);属性 copy 对集合仅浅拷贝,需完全隔离时考虑深拷贝或 copyItems,详见 [11-深浅拷贝与内存](11-主题 | 内存管理@iOS-深浅拷贝与内存.md) |
| Timer | 用 NSProxy 弱引用 self 作 target 破循环,或 iOS 10+ 用 block 版 API;dealloc 前必须 invalidate,详见 [06-weak与循环引用](06-主题 | 内存管理@iOS-weak与循环引用.md) |
| 音视频 | 及时释放解码/采样缓冲,流式加载大文件,后台释放非必要资源,回调中用 weak self |
| 图层/大图 | 大图降采样、控制离屏渲染与 rasterize 缓存、列表复用与按需加载、CGContext 及时释放 |
七、流程图:泄漏排查思路
flowchart LR
A[怀疑泄漏] --> B[Instruments Leaks]
B --> C[看引用链]
C --> D[查循环引用/未移除的观察者等]
D --> E[weak/移除/改设计]
参考文献