内存优化策略
问题
iOS 应用如何进行内存优化?有哪些具体的优化手段?
答案
iOS 内存限制
iOS 应用没有虚拟内存交换(swap),内存不足时系统会直接 kill 应用(OOM / Jetsam)。
| 场景 | 大致限制 |
|---|---|
| 前台应用 | 设备 RAM 的 ~50%(如 4GB 设备 ≈ 2GB) |
| 后台应用 | 极低(几十 MB),超出直接被杀 |
核心优化策略
1. 大图优化
// ❌ 直接加载高分辨率图片 → 占用 width × height × 4 字节
let image = UIImage(named: "huge_photo") // 4000×3000 = 48MB!
// ✅ 下采样:只解码需要的尺寸
func downsample(imageAt url: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage? {
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let options: [CFString: Any] = [
kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels
]
guard let source = CGImageSourceCreateWithURL(url as CFURL, nil),
let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, options as CFDictionary) else {
return nil
}
return UIImage(cgImage: cgImage)
}
2. 内存警告处理
class ImageCache {
private var cache = NSCache<NSString, UIImage>()
init() {
NotificationCenter.default.addObserver(self,
selector: #selector(clearCache),
name: UIApplication.didReceiveMemoryWarningNotification,
object: nil)
}
@objc func clearCache() {
cache.removeAllObjects()
}
}
3. NSCache 替代 Dictionary
// NSCache:
// - 自动在内存不足时淘汰
// - 线程安全
// - 可设置 countLimit / totalCostLimit
let cache = NSCache<NSString, UIImage>()
cache.countLimit = 100
cache.totalCostLimit = 50 * 1024 * 1024 // 50MB
4. autoreleasepool 降低峰值
func processLargeDataset(_ data: [Data]) {
for item in data {
autoreleasepool {
let processed = transform(item) // 临时对象在每次循环结束释放
save(processed)
}
}
}
5. 延迟加载与按需释放
class DetailViewController: UIViewController {
// 延迟加载
lazy var heavyView: HeavyView = {
HeavyView()
}()
// 进入后台时释放非必要资源
@objc func didEnterBackground() {
heavyView.clearCache()
}
}
常见面试问题
Q1: 如何监控应用的内存使用量?
答案:
func currentMemoryUsage() -> UInt64 {
var info = mach_task_basic_info()
var count = mach_msg_type_number_t(MemoryLayout<mach_task_basic_info>.size) / 4
let result = withUnsafeMutablePointer(to: &info) {
$0.withMemoryRebound(to: integer_t.self, capacity: Int(count)) {
task_info(mach_task_self_, task_flavor_t(MACH_TASK_BASIC_INFO), $0, &count)
}
}
return result == KERN_SUCCESS ? info.resident_size : 0
}
Q2: OOM 崩溃如何排查?
答案:OOM 不会产生 crash log。排查方法:
- MetricKit(iOS 14+):
MXForegroundExitData/MXBackgroundExitData提供 OOM 退出信息 - Instruments Allocations:Mark Generation 对比内存增量
- 内存报告:Xcode Organizer → Memory 查看内存峰值
- mmap 大文件替代全量加载
Q3: UIImage 为什么占用那么多内存?
答案:UIImage 解码后的内存 = width × height × 4 bytes(RGBA)。一张 4000×3000 的图片 = 48MB。优化方式:下采样(只解码所需尺寸)、使用 UIGraphicsImageRenderer 而非 UIGraphicsBeginImageContext。