UIImage memory issue

今天要來分享的內容,是有關於 UIImage 的一個記憶體爆炸情況, 而我先闡述一下發現這問題的情境: 專案內有個功能會需要匯入大型圖片做縮放以及拖拉功能, 使用者可以切換大型圖片,而在點擊過多的圖片時,便會造成記憶體爆炸。

原先的做法

在使用者點擊叫出某張圖時,會使用 UIImage(name: ImageName) 來產生 UIImage 物件,並將畫面上的 UIImageView.image 設為它。 看起來蠻合理的,當使用者切換後,我會再生成一個新的 UIImage 物件,並取代前者; 這樣前者就應該會釋放掉記憶體空間了!

但⋯⋯事情並不是這樣發展

在使用者切換幾次後,發現記憶體只有一直往上增長,而未釋放掉; 意思是指雖然我將畫面上的 UIImageView.image 取代掉了, 不過實際上仍然佔據著記憶體空間⋯⋯

為什麼?

上網查了一下後,發現 UIImage(named: ImageName) 這種生成方式, 會自行將取出來的圖片放置到 cache; 而上述的使用情況就會變成當使用者一切換,便會將另一張大型圖片放置到 cache 而未釋放掉前一張。

改良的做法

Data 在建構的時候,有一種選項是 .uncached, 也就是說,我們可以先將圖片以 Data 的方式打開,再轉回 UIImage, 則就可以避免掉它自動放置到 cache 而記憶體爆掉的情況。

if let url = Bundle.main.url(forResource: ImageName, withExtension: ".png"),
    let data = try? Data(contentsOf: url,
                         options: Data.ReadingOptions.uncached) {
        let image = UIImage(data: data)
        imageView.image = image
}

這樣就可以解決 UIImage 的 cache 導致記憶體爆炸的情況。

題外話 至於圖片本身就已經大到放不進來,則可以先 resize 一下:

private func scaleMapImage(_ image: UIImage?, size: CGSize) -> UIImage? {

        guard let image = image else {

            return nil

        }

        UIGraphicsBeginImageContext(CGSize(width: size.width, height: size.height))

        image.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))

        let newImage = UIGraphicsGetImageFromCurrentImageContext()

        UIGraphicsEndImageContext()

        return newImage

    }
comments powered by Disqus