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

iOS替换UIImage时出现内存泄漏问题求助

内存攀升问题排查与解决

核心问题:UIImage(named:)的不当使用

你用UIImage(named: imageURL.path)加载Documents目录的图片,这个方法原本是为加载App Bundle内的资源图片设计的,它会自动将加载后的图片存入系统全局缓存,且该缓存不会随UIImage对象的释放自动清理。每次切换图片时,新图片都会被加入缓存,旧图片无法被回收,直接导致内存持续上涨。

修复方案

替换图片加载方式,使用不触发系统缓存的方法读取文件:

方案1:用UIImage(contentsOfFile:)直接读取

func replaceImage(with imageURL: URL) {
    assert(imageURL.isFileURL)
    
    // 直接从文件路径读取,不触发系统缓存
    guard let nextImage = UIImage(contentsOfFile: imageURL.path) else {
        // 处理图片加载失败的场景
        return
    }

    UIView.transition(
        with: self.imageView,
        duration: 0.1,
        options: .transitionCrossDissolve,
        animations: { self.imageView.image = nextImage },
        completion: nil
    )
}

方案2:通过Data读取

如果需要更灵活的处理(比如验证文件完整性),也可以用Data读取:

func replaceImage(with imageURL: URL) {
    assert(imageURL.isFileURL)
    
    guard let imageData = try? Data(contentsOf: imageURL),
          let nextImage = UIImage(data: imageData) else {
        return
    }

    UIView.transition(
        with: self.imageView,
        duration: 0.1,
        options: .transitionCrossDissolve,
        animations: { self.imageView.image = nextImage },
        completion: nil
    )
}

这两种方式都不会缓存图片,当imageView替换新图后,旧的UIImage对象会被ARC自动回收,内存就能正常释放。

额外注意事项

1. Timer的循环引用问题

你当前用Timer.scheduledTimer(target:)创建的重复Timer会持有self,如果视图控制器(VC)销毁时未手动终止Timer,会导致VC无法被回收,形成隐性内存泄漏。修复方式:

  • 先将Timer保存为实例变量:
    var slideTimer: Timer?
    
  • 在viewDidLoad中初始化:
    slideTimer = Timer.scheduledTimer(
        timeInterval: self.cycleSeconds,
        target: self,
        selector: #selector(displayNext),
        userInfo: nil,
        repeats: true
    )
    
  • 在deinit中销毁Timer:
    deinit {
        slideTimer?.invalidate()
    }
    

或者直接用block式Timer+弱引用,从根源避免循环引用:

Timer.scheduledTimer(withTimeInterval: self.cycleSeconds, repeats: true) { [weak self] _ in
    self?.displayNext()
}

2. 大图片内存优化

如果你的图片分辨率远超过屏幕尺寸,即使不缓存,单张图片也会占用大量内存。可以在加载时对图片进行降采样处理:

func downsampledImage(from url: URL, to targetSize: CGSize) -> UIImage? {
    let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
    guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, imageSourceOptions) else { return nil }
    
    let maxPixelSize = max(targetSize.width, targetSize.height) * UIScreen.main.scale
    let downsampleOptions = [
        kCGImageSourceCreateThumbnailFromImageAlways: true,
        kCGImageSourceCreateThumbnailWithTransform: true,
        kCGImageSourceThumbnailMaxPixelSize: maxPixelSize
    ] as CFDictionary
    
    guard let downsampledCGImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else { return nil }
    return UIImage(cgImage: downsampledCGImage)
}

在replaceImage中调用该方法,传入imageView的尺寸作为目标大小,大幅降低单张图片的内存占用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 22:33:14