You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

UIImage内存未释放异常:点击两次清理才释放的原因探究

SwiftUI 图片清理后内存延迟释放问题

加载图片数据后点击「clean data」按钮清空数据列表,内存并未立即释放,必须连续点击两次按钮,内存才会被回收。分别使用UIImage(contentsOf:)和UIImage(data:)方法加载图片,现象完全一致。

测试代码

import SwiftUI
import Foundation

struct MainView: View {
    @State var data: [Data] = []
    @State var urls: [URL] = []
    
    var body: some View {
        VStack {
            Button("clean data") {
                data.removeAll()
                urls.removeAll()
            }
            Button("load data") {
                loadData()
            }
            HStack {
                ForEach(urls, id: \.self) { url in
                    Image(uiImage: UIImage(contentsOfFile: url.path)!)
                        .resizable()
                        .scaledToFit()
                }
            }
        }
    }
    
    func loadData() {
        for _ in 0...5 {
            urls.append(Bundle.main.url(forResource: "output\(Int.random(in: 0...2))", withExtension: "png")!)
        }
    }
}

Instrument内存监测截图

Instrument内存监测截图

问题原因

  1. SwiftUI视图更新延迟:第一次清空urls后,SwiftUI的视图更新流程尚未完成旧Image视图的销毁,旧视图持有的UIImage实例仍被引用,无法被回收。
  2. UIImage系统缓存:通过文件路径或Data加载的UIImage会被系统自动缓存,即使数据源被清空,缓存的图片对象可能仍未被标记为可回收,第二次清理操作触发了系统缓存的回收逻辑。

解决方案

方案1:强制视图刷新+触发自动释放

在清空数据后,手动触发视图刷新和自动释放池,加速内存回收:

Button("clean data") {
    data.removeAll()
    urls.removeAll()
    // 强制通知视图更新
    objectWillChange.send()
    // 触发自动释放池回收对象
    autoreleasepool { }
}

方案2:用ObservableObject管理图片生命周期

将图片对象托管到可观察对象中,明确控制其生命周期,避免SwiftUI视图隐式持有:

class ImageManager: ObservableObject {
    @Published var images: [UIImage] = []
    
    func loadImages() {
        images.removeAll()
        for _ in 0...5 {
            guard let url = Bundle.main.url(forResource: "output\(Int.random(in: 0...2))", withExtension: "png"),
                  let image = UIImage(contentsOfFile: url.path) else { continue }
            images.append(image)
        }
    }
    
    func cleanImages() {
        images.removeAll()
        autoreleasepool { }
    }
}

struct MainView: View {
    @StateObject var imageManager = ImageManager()
    
    var body: some View {
        VStack {
            Button("clean data") {
                imageManager.cleanImages()
            }
            Button("load data") {
                imageManager.loadImages()
            }
            HStack {
                ForEach(imageManager.images.indices, id: \.self) { index in
                    Image(uiImage: imageManager.images[index])
                        .resizable()
                        .scaledToFit()
                }
            }
        }
    }
}

方案3:绕过UIImage系统缓存

如果不需要系统缓存,可以通过CGImage创建UIImage,避免被系统自动缓存:

// 加载图片时改用此方式
guard let url = Bundle.main.url(forResource: "output\(Int.random(in: 0...2))", withExtension: "png"),
      let data = try? Data(contentsOf: url),
      let cgImage = UIImage(data: data)?.cgImage else { continue }
let image = UIImage(cgImage: cgImage)

内容的提问来源于stack exchange,提问作者Perry Wang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 21:13:17