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

SwiftUI本地数据库加载图片内存溢出问题已解决方案

SwiftUI本地图片加载内存泄漏问题解决

我在SwiftUI项目中加载本地数据库(原基于SQLite3,当前用模拟实现)的图片时,遇到了内存占用无限增长、无法自动回收的问题,存在内存溢出风险。通过以下方案组合优化后,内存稳定在50MB左右,峰值不超过100MB:

  • 使用LazyView包裹图片视图,避免提前加载所有未显示的图片
  • 实现缓存淘汰机制:仅加载当前显示及附近的图片,当内存中缓存的图片数量超过设定最大值时,自动移除最早加载的旧图
  • 利用@StateObject和@Published管理图片加载器,确保不再使用的图片资源能被真正释放

优化后完整代码

数据结构定义

struct Book: Identifiable {
    let id: Int
    let imageName: String
    // 其他书籍属性可在此扩展
}

模拟数据库实现

class BooksDatabase {
    static let shared = BooksDatabase()
    
    func fetchAllBooks() -> [Book] {
        // 模拟从数据库获取上千条书籍数据
        return (1...1000).map { Book(id: $0, imageName: "book_\($0)") }
    }
    
    func loadImage(for book: Book) -> UIImage? {
        // 模拟从本地数据库读取图片(实际场景为SQLite3读取Blob转UIImage)
        return UIImage(named: book.imageName) ?? UIImage(systemName: "book")
    }
}

带缓存淘汰的图片加载器

class ImageLoader: ObservableObject {
    @Published var image: UIImage?
    private let maxCacheCount = 20 // 自定义最大缓存数量
    private static var imageCache: [Int: UIImage] = [:]
    private let book: Book
    private let database = BooksDatabase.shared
    
    init(book: Book) {
        self.book = book
    }
    
    func loadImage() {
        // 优先读取缓存
        if let cachedImage = ImageLoader.imageCache[book.id] {
            self.image = cachedImage
            return
        }
        
        // 从数据库加载图片
        guard let loadedImage = database.loadImage(for: book) else { return }
        
        // 缓存数量超限,移除最早加入的缓存项
        if ImageLoader.imageCache.count >= maxCacheCount {
            if let firstKey = ImageLoader.imageCache.keys.sorted().first {
                ImageLoader.imageCache.removeValue(forKey: firstKey)
            }
        }
        
        // 更新缓存与视图
        ImageLoader.imageCache[book.id] = loadedImage
        self.image = loadedImage
    }
    
    // 视图销毁时清理当前图片缓存
    func clearCacheForCurrentBook() {
        ImageLoader.imageCache.removeValue(forKey: book.id)
    }
}

LazyView实现

struct LazyView<Content: View>: View {
    let build: () -> Content
    init(_ build: @autoclosure @escaping () -> Content) {
        self.build = build
    }
    
    var body: some View {
        build()
    }
}

主滚动视图(ScrollShowView)

struct ScrollShowView: View {
    private let books = BooksDatabase.shared.fetchAllBooks()
    
    var body: some View {
        ScrollView {
            LazyVStack(spacing: 16) {
                ForEach(books) { book in
                    LazyView(
                        BookImageView(book: book)
                    )
                }
            }
            .padding()
        }
    }
}

struct BookImageView: View {
    let book: Book
    @StateObject private var imageLoader: ImageLoader
    
    init(book: Book) {
        self.book = book
        self._imageLoader = StateObject(wrappedValue: ImageLoader(book: book))
    }
    
    var body: some View {
        Group {
            if let image = imageLoader.image {
                Image(uiImage: image)
                    .resizable()
                    .scaledToFit()
                    .frame(height: 200)
            } else {
                ProgressView()
                    .frame(height: 200)
            }
        }
        .onAppear {
            imageLoader.loadImage()
        }
        .onDisappear {
            imageLoader.clearCacheForCurrentBook()
        }
    }
}

此前尝试的无效方案

  • 使用autoreleasepool包裹图片加载逻辑:无法解决SwiftUI视图层级中对图片资源的强引用持有问题
  • 将LazyVStack替换为VStack:会一次性加载所有列表图片,直接加剧内存占用问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 00:47:20