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
相关产品推荐
相关产品推荐

