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

iOS中用UISlider更新UIImageView引发内存持续上涨问题求助

内存泄漏问题:拖动滑块切换PNG图片导致真机内存暴涨崩溃

问题场景与现象

  • 界面结构:ScrollView嵌套UIView,UIView包含两个UIImageView,背景显示JPG格式奇幻地图,前景显示带透明背景的PNG路径图
  • 交互逻辑:通过UISlider选择日期,切换对应日期的PNG前景图(共104张)
  • 异常表现:真机调试时拖动滑块内存持续上涨最终崩溃,模拟器无此问题;但在viewDidLoad中循环调用104次图片切换方法,内存使用正常

问题代码

import UIKit

class MapViewController: UIViewController {
    @IBOutlet weak var scrollView: UIScrollView!
    @IBOutlet weak var imagesView: UIView!
    @IBOutlet weak var backgroundImageView: UIImageView!
    @IBOutlet weak var foregroundImageView: UIImageView!
    @IBOutlet weak var menuStackView: UIStackView!
    @IBOutlet weak var coloredSwitch: UISwitch!
    @IBOutlet weak var dateLabel: UILabel!
    @IBOutlet weak var dateSlider: UISlider!
    
    let mapPins = MapPins()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        menuStackView.layer.cornerRadius = 11.0
        menuStackView.layer.masksToBounds = true
        
        dateSlider.maximumValue = Float(mapPins.pinsLabelTexts.count)
        
        scrollView.delegate = self
        
        updateBackgroundMap(isColored: true)
    }
    
    func updateBackgroundMap(isColored: Bool) {
        var backImage: UIImage? = nil
        if isColored {
            backImage = UIImage(named: "Mappa Antico Mondo - colori.jpg")
        } else {
            backImage = UIImage(named: "Mappa Antico Mondo - bn.jpg")
        }
        backgroundImageView.image = backImage
        backgroundImageView.contentMode = .scaleAspectFit
        backgroundImageView.frame.size = backImage!.size
    }
    
    func updateTopImage(imageName: String?) {
        if imageName == nil {
            foregroundImageView.image = nil
        } else {
            let image = UIImage(named: imageName!)
            foregroundImageView.image = image
        }
    }
    
    func updateCharactersPins() {
        var index = Int(dateSlider.value)
        if index > mapPins.pinsLabelTexts.count-1 {
            index = mapPins.pinsLabelTexts.count-1
        }
        dateLabel.text = mapPins.pinsLabelTexts[index-1]
        updateTopImage(imageName: mapPins.getPinsImageName(for: index-1))
    }
    
    @IBAction func toggleColors(_ sender: UISwitch) {
        updateBackgroundMap(isColored: sender.isOn)
    }
    @IBAction func dateSliderChanged(_ sender: UISlider) {
        updateCharactersPins()
    }
}

extension MapViewController: UIScrollViewDelegate {
    
    func viewForZooming(in scrollView: UIScrollView) -> UIView? {
        return imagesView
    }
}

问题原因分析

  • UIImage(named:)方法会自动将加载的图片缓存到系统全局NSCache中,拖动滑块时会高频触发图片加载,系统缓存不会及时释放这些图片(真机内存远小于模拟器,缓存堆积更快)
  • viewDidLoad循环调用时,图片加载是一次性完成的,系统有时间在后台清理未被引用的缓存图片,因此内存表现正常

解决方案

1. 改用非缓存方式加载图片

使用UIImage(contentsOfFile:)替代UIImage(named:),该方法不会自动缓存图片,图片使用完毕后会被ARC及时回收:

func updateTopImage(imageName: String?) {
    // 先清空旧图片,加速内存释放
    foregroundImageView.image = nil
    
    guard let imageName = imageName else { return }
    
    // 获取图片的本地文件路径(假设imageName不带.png后缀)
    if let imagePath = Bundle.main.path(forResource: imageName, ofType: "png") {
        let image = UIImage(contentsOfFile: imagePath)
        foregroundImageView.image = image
    }
}

2. 优化滑块触发频率(防抖处理)

拖动滑块时会多次触发dateSliderChanged,添加防抖逻辑,仅在拖动停止后执行一次图片更新,减少不必要的加载:

class MapViewController: UIViewController {
    // 添加定时器属性
    private var updateTimer: Timer?
    
    // ... 其他原有代码 ...
    
    @IBAction func dateSliderChanged(_ sender: UISlider) {
        // 取消之前的定时器,重置延迟
        updateTimer?.invalidate()
        // 延迟0.1秒执行,拖动停止后才会触发更新
        updateTimer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false) { [weak self] _ in
            self?.updateCharactersPins()
        }
    }
    
    // 销毁控制器时清理定时器
    deinit {
        updateTimer?.invalidate()
    }
}

3. 额外优化:手动清理背景图缓存(可选)

如果背景图也存在内存问题,同样可以改用contentsOfFile加载,或者在切换背景图时手动清空旧图片:

func updateBackgroundMap(isColored: Bool) {
    // 先清空旧背景图
    backgroundImageView.image = nil
    
    let imageName = isColored ? "Mappa Antico Mondo - colori.jpg" : "Mappa Antico Mondo - bn.jpg"
    if let imagePath = Bundle.main.path(forResource: imageName, ofType: nil) {
        let backImage = UIImage(contentsOfFile: imagePath)
        backgroundImageView.image = backImage
        backgroundImageView.contentMode = .scaleAspectFit
        backgroundImageView.frame.size = backImage!.size
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 09:20:55