Swift iOS App 下載圖片的幾種方法

開發 iOS App 時,我們時常需要撰寫下載圖片的程式,常見的抓圖程式是利用我們熟悉的 URLSessionDataTask,例如以下點選 button 下載圖片的例子:

@IBAction func download(_ sender: Any) {
imageView.image = nil
let url = URL(string: "https://images-na.ssl-images-amazon.com/images/I/51f-7KjjFeL._SX317_BO1,204,203,200_.jpg")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let data,
let image = UIImage(data: data) {
DispatchQueue.main.async {
self.imageView.image = image
}
}
}.resume()
}

預設 cache 的小圖

當圖片是小圖時(剛剛程式裡的圖片大概 46 kB),iOS 還會幫我們 cache,讓我們下次抓取一樣的圖時可以直接讀 cache,不用再重抓等待。比方剛剛的程式執行後,我們可利用 NSHomeDirectory() 找出 App 資料夾的路徑,然後從裡面找到暫存的圖檔。

/Users/shih-yingpan/Library/Developer/CoreSimulator/Devices/43C39FF6-341C-4CFE-B987-14E1A188DDA8/data/Containers/Data/Application/217FE133-2A4D-4516-BAF8-7166D980B1A5

從 Finder 打開 App 的資料夾,在它的 Library/Caches/com.peter.DemoCache/fsCacheData/ 下可找到圖片。(com.peter.DemoCache 是 App 的 bundle ID)

不會 cache 的大圖

不過當圖片太大時,就沒那麼幸運了。這時候 iOS 不會幫我們 cache,所以每次都要重新下載。

範例: 690 kB 的大圖網址

https://i.pinimg.com/originals/df/80/f3/df80f367ffb8669baeabcd5564f1b638.jpg

除了從點選 button 下載同一張圖,圖片還是要過一會才顯示得知沒有 cache 外,我們也可從 Finder 打開 App 的資料夾,發現裡面沒有大圖的圖檔。

增加 cache 容量讓大圖也能 cache

原本大圖不會被 cache 是因為預設的 cache 容量不夠大,因此我們只要在 AppDelegate 的 application(_:didFinishLaunchingWithOptions:) 加入以下程式,增加 memory & disk cache 的容量,即可讓大圖也能 cache。

  • 方法 1: 修改 URLCache.shared
URLCache.shared.memoryCapacity = 25_000_000
URLCache.shared.diskCapacity = 100_000_000
  • 方法 2: 產生新的 URLCache

diskPath 也可以傳入 nil,此時資料將存在預設路徑的資料夾。

let temporaryDirectory = NSTemporaryDirectory()
let urlCache = URLCache(memoryCapacity: 25_000_000, diskCapacity: 50_000_000, diskPath: temporaryDirectory)
URLCache.shared = urlCache

此時點選 button 下載圖片,從 Finder 打開 App 的資料夾,終於發現大圖的圖檔了。因為大圖被 cache,所以現在我們點選 button 下載同一張圖時,將發現速度快很多,圖片幾乎馬上出現,不過還是會有閃一下的小小缺點。

利用 NSCache 暫存網路圖片

想要有更好的效果,希望點選 button 下載同一張圖時,連閃都不會閃嗎?

其實是做得到的 ! 我們可以利用以下連結介紹的 NSCache 暫存圖片,或是將圖片暫存在 temporary directory。

將圖片暫存在 temporary directory

每次下載前先檢查圖檔是否已存在,如果已存在就直接讀檔,不需再啟動 URLSessionDataTask。

@IBAction func download(_ sender: Any) {
let url = URL(string: "https://i.pinimg.com/originals/df/80/f3/df80f367ffb8669baeabcd5564f1b638.jpg")!
let tempDirectory = FileManager.default.temporaryDirectory
let imageFileUrl = tempDirectory.appendingPathComponent(url.lastPathComponent)
if FileManager.default.fileExists(atPath: imageFileUrl.path) {
let image = UIImage(contentsOfFile: imageFileUrl.path)
self.imageView.image = image
} else {
self.imageView.image = nil
URLSession.shared.dataTask(with: url) { data, response, error in
if let data,
let image = UIImage(data: data) {
try? data.write(to: imageFileUrl)
DispatchQueue.main.async {
self.imageView.image = image
}
}
}.resume()
}
}

使用抓圖套件

如果懶得自己寫程式抓圖,採用套件也是不錯的方法,像知名的套件 Kingfisher & SDWebImage 都跟剛剛自己將圖暫存在 temporary directory 有一樣好的效果,而且套件本身還加了許多進階的功能。

如下圖所示,Kingfisher 套件一樣會將圖案存在 cache 裡,不過它會在 Caches 下另建資料夾 com.onevcat.Kingfisher.ImageCache.default。

研究 Kingfisher 的程式碼後,發現它果然會從 disk cache 讀取圖片,如果讀不到才會連網抓圖。

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com