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

SwiftUI模态Sheet展示大图片关闭后内存未完全释放如何解决

问题原因
  • 内存暴涨的核心是加载了全尺寸原始图片。5184*3456分辨率的图片按RGBA格式解码后,单张位图的内存占用为5184 * 3456 * 4字节 ≈ 68MB,加上sheet视图本身的渲染开销、临时缓冲区占用,内存涨到100MB是符合预期的。你虽然给Image设置了仅为屏幕1/4高度的显示尺寸,但resizable()仅调整布局渲染的拉伸规则,不会改变系统加载的原图解码尺寸,等于把几十MB的大图缩到小尺寸显示,存在严重的内存浪费。
  • 关闭sheet后内存没有回落到初始20MB,是系统图片缓存机制导致的。通过Image("image")直接从资源目录加载的图片,系统会把解码后的位图存入全局共享缓存,不会因为对应视图销毁就立刻释放,这块缓存会在系统内存紧张时自动回收,不属于内存泄漏。你看到关闭后内存从100MB降到92MB,是sheet的视图实例、临时渲染缓冲区被释放了,剩下的占比大头就是系统缓存的全尺寸图片。
  • 反复开关sheet内存稳定在92MB-100MB区间,是因为第二次打开时系统直接从缓存取已经解码好的图片,不需要重复解码,内存只会叠加视图的临时开销,不会持续上涨。
优化方案
  • 核心思路是不要加载和显示尺寸不匹配的全尺寸原图,对图片做下采样处理,让解码后的位图尺寸和实际显示区域的像素尺寸完全匹配,从根源降低内存占用。
  • 下采样通过ImageIO实现,加载时直接指定需要的最大像素尺寸,避免全量解码原图。处理后同一张图的内存占用可以从68MB降到几MB级别。
  • 视图销毁时主动释放自己持有的下采样图片对象,不要依赖系统全局缓存做资源持有,保证视图消失后对应内存能立刻回收。
  • 可选优化:如果图片是固定放在Assets里的静态资源,可以提前切好对应显示尺寸的2x/3x缩略图资源,直接加载对应尺寸的资源即可,不需要运行时做下采样。

优化后的参考实现:

import SwiftUI
import ImageIO
import UIKit

struct IntroductionView: View {
    @Environment(\.presentationMode) var presentation
    // 仅持有和显示尺寸匹配的下采样图片
    @State private var displayImage: UIImage?
    // 对应本地图片路径
    private let imageURL = Bundle.main.url(forResource: "image", withExtension: "jpg")!
    
    // 按指定最大高度做图片下采样
    func downsampleImage(maxHeight: CGFloat, scale: CGFloat) -> UIImage? {
        let maxPixelSize = maxHeight * scale
        let sourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, sourceOptions) else { return nil }
        let downsampleOptions = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceThumbnailMaxPixelSize: maxPixelSize,
            kCGImageSourceCreateThumbnailWithTransform: true
        ] as CFDictionary
        guard let cgImage = CGImageSourceCreateThumbnailAtIndex(source, 0, downsampleOptions) else { return nil }
        return UIImage(cgImage: cgImage)
    }
    
    var body: some View {
        GeometryReader{ geo in
            VStack(alignment:.leading){
                Group {
                    if let displayImage {
                        Image(uiImage: displayImage)
                            .resizable()
                            .aspectRatio(contentMode: .fill)
                    } else {
                        Color.gray // 图片加载占位
                    }
                }
                .frame(maxHeight:geo.size.height/4)
                .clipped()
                .onAppear {
                    // 视图出现时按实际显示尺寸下采样图片
                    displayImage = downsampleImage(
                        maxHeight: geo.size.height/4,
                        scale: UIScreen.main.scale
                    )
                }
                .onDisappear {
                    // 视图消失时主动释放图片资源
                    displayImage = nil
                }
                
                Spacer()
                Button(action: {
                    presentation.wrappedValue.dismiss()
                }, label: {
                    Text("Close")
                })
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 11:15:42