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

SwiftUI中列表内AsyncImage滚动时内存过高致崩溃求助

问题分析与解决方案

核心问题是AsyncImage默认加载原图且未做内存优化,当List滚动加载大量图片时,内存占用飙升至1GB以上导致崩溃。以下是针对性优化方案:

1. 限制图片渲染尺寸

AsyncImage默认加载图片原始尺寸,即便显示区域很小也会占用大量内存。通过设置缩放比例和固定显示框架,减少实际渲染的像素量:

AsyncImage(url: URL(string: imageURLArray[i]), scale: UIScreen.main.scale) { image in
    image.resizable()
        .scaledToFit()
        .frame(maxHeight: 200) // 固定最大高度,压缩渲染像素
        .cornerRadius(10, antialiased: true)
        .padding(.horizontal)
        .padding(.vertical, 5)
} placeholder: {
    ZStack {
        RoundedRectangle(cornerRadius: 10)
            .foregroundColor(.black.opacity(0.1))
            .aspectRatio(16/9, contentMode: .fit)
            .padding(.horizontal)
            .padding(.vertical, 10)
        ProgressView()
            .progressViewStyle(.circular)
    }
}

2. 自定义图片缓存限制

通过全局配置URLCache,限制内存缓存的最大容量,避免大量图片持续占用内存:

// 在App启动代码中添加(比如AppDelegate或SceneDelegate)
let memoryCacheSize = 50 * 1024 * 1024 // 50MB内存缓存上限
let diskCacheSize = 200 * 1024 * 1024 // 200MB磁盘缓存上限
URLCache.shared = URLCache(
    memoryCapacity: memoryCacheSize,
    diskCapacity: diskCacheSize,
    diskPath: "com.yourapp.image-cache"
)

3. 优化List视图复用

把ForEach的遍历对象从索引改为URL字符串本身,避免索引变化导致视图频繁重建,提升List的复用效率:

List {
    ForEach(imageURLArray, id: \.self) { urlString in
        AsyncImage(url: URL(string: urlString)) { image in
            image.resizable()
                .aspectRatio(16/9, contentMode: .fit)
                .cornerRadius(10, antialiased: true)
                .padding(.horizontal)
                .padding(.vertical, 5)
        } placeholder: {
            // 保持原占位符代码
        }
        .onAppear {
            if urlString == imageURLArray.last {
                loadMoreItems()
            }
        }
    }
    .listRowInsets(EdgeInsets())
}

4. 自定义图片加载器(进阶优化)

如果上述方案仍无法解决内存问题,可以手动实现图片加载逻辑,在加载后直接压缩图片尺寸,并在视图消失时主动释放内存:

struct CustomImageView: View {
    let urlString: String
    @State private var image: UIImage?
    
    var body: some View {
        Group {
            if let image = image {
                Image(uiImage: image)
                    .resizable()
                    .aspectRatio(16/9, contentMode: .fit)
                    .cornerRadius(10, antialiased: true)
                    .padding(.horizontal)
                    .padding(.vertical, 5)
            } else {
                // 原占位符代码
                ZStack {
                    RoundedRectangle(cornerRadius: 10)
                        .foregroundColor(.black.opacity(0.1))
                        .aspectRatio(16/9, contentMode: .fit)
                        .padding(.horizontal)
                        .padding(.vertical, 10)
                    ProgressView()
                        .progressViewStyle(.circular)
                }
            }
        }
        .onAppear { loadImage() }
        .onDisappear { image = nil } // 视图消失时释放图片内存
    }
    
    private func loadImage() {
        guard let url = URL(string: urlString) else { return }
        URLSession.shared.dataTask(with: url) { data, _, _ in
            guard let data = data, let rawImage = UIImage(data: data) else { return }
            // 压缩图片到目标显示尺寸
            let targetSize = CGSize(
                width: UIScreen.main.bounds.width - 40,
                height: (UIScreen.main.bounds.width - 40) * 9 / 16
            )
            let resizedImage = rawImage.resized(to: targetSize)
            DispatchQueue.main.async { self.image = resizedImage }
        }.resume()
    }
}

// UIImage扩展:调整图片尺寸
extension UIImage {
    func resized(to size: CGSize) -> UIImage? {
        UIGraphicsBeginImageContextWithOptions(size, false, UIScreen.main.scale)
        defer { UIGraphicsEndImageContext() }
        draw(in: CGRect(origin: .zero, size: size))
        return UIGraphicsGetImageFromCurrentImageContext()
    }
}

使用时直接替换原AsyncImage:

List {
    ForEach(imageURLArray, id: \.self) { urlString in
        CustomImageView(urlString: urlString)
            .onAppear {
                if urlString == imageURLArray.last {
                    loadMoreItems()
                }
            }
    }
    .listRowInsets(EdgeInsets())
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 09:42:54