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
相关产品推荐
相关产品推荐

