はじめに
iOSのアプリを開発していて画像をどこで保持しておくか悩むことがありました。そこでメモリとディスクで画像の読み込み速度を計測・比較してみようと思い立ちました。
そもそもキャッシュとは
ファイルやアプリケーションのデータを一時的に保持し、データの再取得や再計算にかかる時間を短縮する仕組みです。
メモリキャッシュとディスクキャッシュ
メモリキャッシュ
データをデバイスのRAM(ランダムアクセスメモリ)内に一時的に保存する方法です。高速にアクセスできますが、アプリケーションが終了するか、デバイスが再起動されると消去されます。使用し過ぎると動作が重くなったりもします。
ディスクキャッシュ
データをデバイスの永続的なストレージ(SSDやHDDなど)に保存する方法です。ディスクキャッシュはメモリキャッシュよりも遅いですがアプリケーションが終了してもデータは保持されます。ストレージの一部を使用します。iPhoneの128GBとか256GBとかいってるあれです。
キャッシュに画像を保存する
メモリキャッシュへ保存
メモリキャッシュには、通常、NSCache
を使用します。これにより、アプリが実行中は画像がメモリ内に保持され、アクセスが高速になります。ライブラリも豊富にありますね。
import UIKit
class ImageCacheManager {
static let shared = ImageCacheManager()
private var imageCache = NSCache<NSString, UIImage>()
func loadImage(forKey key: String) -> UIImage? {
return imageCache.object(forKey: NSString(string: key))
}
func saveImage(_ image: UIImage, forKey key: String) {
imageCache.setObject(image, forKey: NSString(string: key))
}
}
ディスクキャッシュへ保存
ディスクキャッシュには、画像をファイルシステムに保存します。FileManager
を使用してディスクに保存し、必要に応じて読み込みます。
import UIKit
class DiskCacheManager {
static let shared = DiskCacheManager()
private let fileManager = FileManager.default
private let cacheDirectory = FileManager.default.urls(for: .cachesDirectory, in: .userDomainMask)[0]
func loadImage(forKey key: String) -> UIImage? {
let fileURL = cacheDirectory.appendingPathComponent(key)
guard let imageData = try? Data(contentsOf: fileURL) else { return nil }
return UIImage(data: imageData)
}
func saveImage(_ image: UIImage, forKey key: String) {
let fileURL = cacheDirectory.appendingPathComponent(key)
if let data = image.jpegData(compressionQuality: 1.0) {
try? data.write(to: fileURL)
}
}
}
比較テスト
いま紹介したメソッドを使用して画像1枚を読み込み、それぞれの方法でどれだけ時間がかかるかを測定します。
func testImageLoading() {
let testImageKey = "test"
// ディスクから画像をロード
let startDisk = Date()
let diskImage = DiskCacheManager.shared.loadImage(forKey: testImageKey)
let endDisk = Date()
print("ディスク: \(endDisk.timeIntervalSince(startDisk))秒")
// メモリから画像をロード
let startMemory = Date()
let memoryImage = ImageCacheManager.shared.loadImage(forKey: testImageKey)
let endMemory = Date()
print("メモリ: \(endMemory.timeIntervalSince(startMemory))秒")
}
結果
メモリキャッシュ:約0.000007秒 - 7マイクロ秒
ディスクキャッシュ:約0.0026秒 - 2.6ミリ秒
約370倍の差があると分かりました。
ディスクキャッシュでも十分早く感じますが圧倒的にメモリキャッシュの方が早いですね。
ただしデータサイズやデバイスのスペック次第で変わってくるかもしれません。
プリフェッチ
話が変わりますがプリフェッチについて紹介します。読んで字の如く、事前に取得(pre-fetch)する概念です。取得に時間がかかるなら先に用意しておこう!ってやつです。iOSのキャッシュライブラリにはプリフェッチがメソッドとして用意されているものもあるので必要に応じて使っていきたいです。
import Kingfisher
let imageUrls = [URL...]
let prefetcher = ImagePrefetcher(urls: imageUrls, options: nil, progressBlock: nil, completionHandler: {
skippedResources, failedResources, completedResources in
})
prefetcher.start()
まとめ
ディスクキャッシュよりメモリキャッシュの方が早いと知っているだけで、どれくらい早いのかまで知りませんでした。今回このような形で知ることができたのは良い思い立ちでした。
最後に
私の働いている会社で経験の有無を問わず採用を行っています。
興味のある方は是非カジュアル面談から応募してみてください!