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

SwiftUI中使用UIKit展示图片时内存泄漏的解决方法咨询

解决SwiftUI LazyHStack中UIKit图片视图内存无法释放的问题

问题核心原因

  1. UIViewController生命周期未正确清理:封装的TestImageDisplay控制器被SwiftUI卸载时,内部的UIImageView和加载的UIImage因强引用未被及时回收,导致内存泄漏。
  2. 图片加载未做内存优化:直接加载原始大尺寸图片会占用大量内存,即便视图卸载,未清空的图片实例仍会持续占用内存。
  3. SDWebImage请求未及时取消:滑动时未取消不可见视图的图片加载请求,且未清理已加载的图片缓存引用。

解决方案步骤

1. 重构UIViewController,添加销毁时的清理逻辑

将UIImageView改为实例变量,在视图消失和控制器销毁时主动清空图片、取消加载请求,确保ARC能正常回收内存:

import UIKit
import SwiftUI
import SDWebImage

class TestImageDisplay: UIViewController {
    
    var item: TestItem
    private let imageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))
    
    init(item: TestItem) {
        self.item = item
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        imageView.center = view.center
        view.addSubview(imageView)
        loadImage()
    }
    
    private func loadImage() {
        // 配置SDWebImage选项:优先解码、自动缩小大图,减少内存占用
        let options: SDWebImageOptions = [.decodeFirst, .scaleDownLargeImages]
        imageView.sd_setImage(with: item.imageURL, placeholder: nil, options: options)
    }
    
    // 视图即将消失时,取消请求并清空图片
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        imageView.sd_cancelCurrentImageLoad()
        imageView.image = nil
    }
    
    // 验证控制器是否被销毁(可选,用于调试)
    deinit {
        print("TestImageDisplay deinit: \(item.id)")
        imageView.image = nil
    }
}

2. 优化UIViewControllerRepresentable的更新逻辑

确保仅当item变化时才重新加载图片,避免不必要的资源消耗:

struct TestImageDisplayRepresentable: UIViewControllerRepresentable {
    var item: TestItem
    
    func makeUIViewController(context: Context) -> TestImageDisplay {
        TestImageDisplay(item: item)
    }
    
    func updateUIViewController(_ uiViewController: TestImageDisplay, context: Context) {
        guard uiViewController.item.id != item.id else { return }
        uiViewController.item = item
        uiViewController.loadImage()
    }
}

3. 针对UIImage(contentsOfFile:)的优化

如果不用SDWebImage,手动加载图片时需异步处理+尺寸压缩,同时同样在销毁时清空图片:

// 替换TestImageDisplay中的loadImage方法
private func loadImage() {
    guard let filePath = item.imageURL.path else { return }
    DispatchQueue.global(qos: .userInitiated).async {
        // 加载并压缩图片到目标尺寸
        guard let originalImage = UIImage(contentsOfFile: filePath),
              let resizedImage = originalImage.resized(to: CGSize(width: 200, height: 200)) else {
            return
        }
        DispatchQueue.main.async {
            self.imageView.image = resizedImage
        }
    }
}

// UIImage扩展:压缩图片
extension UIImage {
    func resized(to size: CGSize) -> UIImage? {
        let renderer = UIGraphicsImageRenderer(size: size)
        return renderer.image { context in
            self.draw(in: CGRect(origin: .zero, size: size))
        }
    }
}

额外优化建议

  • SDWebImage缓存配置:设置合理的内存缓存上限,开启自动清理过期数据:
    let cache = SDWebImageManager.shared.imageCache
    cache.config.maxMemoryCost = 100 * 1024 * 1024 // 100MB内存缓存上限
    cache.config.shouldRemoveExpiredDataOnDisk = true
    
  • SwiftUI滚动行为优化:确保ScrollView使用合适的滚动目标行为,让LazyHStack正确卸载不可见视图:
    ScrollView(.horizontal, showsIndicators: false) {
        LazyHStack(spacing: 16) {
            // ... 原有代码
        }
        .scrollTargetLayout()
    }
    .scrollTargetBehavior(.viewAligned) // 确保视图对齐滚动,触发LazyHStack的视图卸载
    
  • 避免循环引用:如果在闭包中引用self,必须使用weak self或unowned self,防止内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 06:05:54