今天要來分享的內容,是有關於 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
}