目的
画像をキャッシュするための方法がわからず、NSUserDefaultsに保存しまくっていたらメモリ不足でアプリが立ち上がらなくなった。
安全なキャッシュの仕方について調べていると結構勉強になったので、その時のメモをまとめました。基本的にはAlamofireImageリポジトリのREADMEを読めばOK。
https://github.com/Alamofire/AlamofireImage#image-caching
NSURLCache
iOSデフォルトのキャッシュはこれ。意外と高機能で基本的な使い方であれば十分に役割を果たせる
AlamofireImage::ImageCache
AlamofireImageによるNSURLCacheの拡張
ダウンロードしてきた画像を加工したもの(後述のImage Filtersを適用した結果の画像)をキャッシュすることもできる。
例えば、プロフィール用の画像をダウンロードしてきた時、オリジナルの画像とそれを加工してサムネイルのサイズとして使いたい場合に、それぞれの画像をキャッシュしておくことができる。NSURLCacheの場合はオリジナルの画像をキャッシュすることしかできない。
キャッシュの可否 | NSURLCache | ImageCache(AlamofireImage) |
---|---|---|
ダウンロードした画像 | ◯ | ◯ |
フィルター後の画像 | ☓ | ◯ |
ImageCache
AlamofireImageを利用して画像をキャッシュするときには以下の4つのパラメータを調整することができる。
NSURLCacheのパラメータ
- diskCapacity(default: 150 MB)
- memoryCapacity(default: 30 MB)
let cache = NSURLCache(
memoryCapacity: 20 * 1024 * 1024, // 20 MB
diskCapacity: 150 * 1024 * 1024, // 150 MB
diskPath: "com.alamofire.imagedownloader"
)
Alamofire::ImageCacheのパラメータ
- memoryCapacity(default: 100 MB) フィルター済みの画像もキャッシュされる
- preferredMemoryUsageAfterPurge(default: 60 MB) memoryCapacityを超えた時にキャッシュの画像を削除した後のキャッシュサイズの上限
let imageCache = AutoPurgingImageCache(
memoryCapacity: 100 * 1024 * 1024, // 100 MB
preferredMemoryUsageAfterPurge: 60 * 1024 * 1024 // 60 MB
)
ImageFilterを使わない場合はNSURLCacheのmemoryCapacityを0にするべき。同じ画像がNSURLCacheとImageCacheに重複して保存されてしまうので。
Add/Remove/Fetch Images
取得した画像をindentifierを指定してキャッシュする
URL Requests (Add/Remove/Fetch Images)
取得した画像をURLをindentifierとしてキャッシュする。独自のidentifierを指定することもできる。
いちいちidentifierを考えなくていいの、こちらのほうが使い勝手がいいと思う。
let imageCache = AutoPurgingImageCache()
let URLRequest = NSURLRequest(URL: NSURL(string: "https://httpbin.org/image/png")!)
let avatarImage = UIImage(named: "avatar")!.af_imageRoundedIntoCircle()
// Add
imageCache.addImage(
avatarImage,
forRequest: URLRequest
)
// Fetch
let cachedAvatarImage = imageCache.imageForRequest(
URLRequest
)
// Remove
imageCache.removeImageForRequest(
URLRequest
)
Auto Purging
キャッシュされている画像にアクセスが有るたびに、画像のlastAccessDate
が更新される。キャッシュされているサイズがmemoryCapacity
以上になったばあいはサイズがpreferredMemoryCapacityAfterPurge
以下になるまでlastAccessDate
が古い順にキャッシュから画像が削除される。
Image Downloader
Download an Image
let downloader = ImageDownloader()
let URLRequest = NSURLRequest(URL: NSURL(string: "https://httpbin.org/image/jpeg")!)
downloader.downloadImage(URLRequest: URLRequest) { response in
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print(image)
}
}
Applying an ImageFilter
let downloader = ImageDownloader()
let URLRequest = NSURLRequest(URL: NSURL(string: "https://httpbin.org/image/jpeg")!)
let filter = AspectScaledToFillSizeCircleFilter(size: CGSize(width: 100.0, height: 100.0))
downloader.downloadImage(URLRequest: URLRequest, filter: filter) { response in
print(response.request)
print(response.response)
debugPrint(response.result)
if let image = response.result.value {
print(image)
}
}
Authentication
ダウンロードするときにbasic認証がある場合につかう
let downloader = ImageDownloader()
downloader.addAuthentication(user: "username", password: "password")
Download Prioritization
ImageDownloaderが画像をダウンロードするときのキューの管理方法を指定する。
- FIFO
- LIFO
Image Caching
ImageDownloaderを使って画像をダウンロードした際はNSURLCache
とAutoPurgingImageCache
がともに使われる
NSURLCache
NSURLCacheによるキャッシュを使いたくない場合は
let configuration = NSURLSessionConfiguration()
configuration.URLCache = nil
let imageDownloader = ImageDownloader(
configuration: configuration, // default(ImageDownloader.defaultURLSessionConfiguration())
downloadPrioritization: .FIFO,
maximumActiveDownloads: 4,
imageCache: AutoPurgingImageCache()
)
ImageCache
ダウンロードした画像と、フィルターをかけた画像の両方をキャッシュする
Duplicate Downloads
ときどき同じリクエストを複数回実行してしまうらしいが、それをエレガントに解決してくれるらしい。
completion handlersは複数回呼ばれてしまうから気をつけてね。とのこと
UIImageView Extension
UIImage Extensions
, Image Filters
, Image Cache
, Image Downloader
を利用して実装されている。
Setting Image with URL
リクエストを投げる前にキャッシュがあるかどうかを調べてくれる。キャッシュがある場合はリクエストを投げずにその画像を使う。
identifier
にはURLRequest.URLString
を使っている。filter
をかけている場合は URLRequest.URLString
+ filter.identifier
を使っている。
Placeholder Images
画像を取得するまでの間はこの画像が使用される。画像がキャッシュされていた場合はこの画像は使われない。
Image Filters
filter
がセットされていた場合は画像がダウンロードされた直後にfilter
適用される。キャッシュがある場合はそれが返される。
Image Transiotions
画像が取得された直後にアニメーションがつく。キャッシュがあるときはimage transision
は無視される。
Image Downloader
UIIMageView extension
は デフォルトのImageDownloader
インスタンスを利用している。キャッシュサイズなどを変更したい場合はImageDownloader
の使い方を参照する。
Authentication
ImageDownloader.defaultInstance.addAuthentication(
user: "user",
password: "password"
)
imageView.af_setImageWithURL(....)
とすればOK
【おまけ】 AlamofireImageを使った画像の格好
UIImage Extensions
Inflation
JPGやPNGは圧縮されているので、それを展開する?
PNGは可逆圧縮だけど、JPGは非可逆圧縮のはず。なのに展開できるとはどういうこと?
Scaling
縦横を指定して画像サイズを変更したり、アスペクト比を維持したまま画像サイズを変更できる
Rounded Corners
角丸をつける
Core Image Filters
CoreImageを使ってフィルターをかけることができる
Image Filters
UIImageExtensionのCoreImageFilterと異なる点は、フィルター後の画像をキャッシュすることができること。
AlamofireImageで独自に用意されているフィルターの仕組みを利用している。
Single Pass
- ScaledToSizeFilter - 縦横のサイズを指定してリサイズ
- AspectScaledToFitSizeFilter - アスペクト比を維持してImageView内に収まるように拡大
- AspectScaledToFillSizeFilter - アスペクト比を維持してImageViewをギリギリ埋め尽くすように拡大
- RoundedCornersFilter - radiusを指定して角丸にする
- CircleFilter - 画像を円形にする
- BlurFilter - CIGaussianBlurを適応する
Multi-Pass
複数のSingle Passを組み合わせたもの
- ScaledToSizeWithRoundedCornersFilter - ScaledToSizeFilter + RoundedCornersFilter
- AspectScaledToFillSizeWithRoundedCornersFilter - AspectScaledToFillSizeFilter + RoundedCornersFilter
- ScaledToSizeCircleFilter - ScaledToSizeFilter + CircleFilter
- AspectScaledToFillSizeCircleFilter - AspectScaledToFillSizeFilter + CircleFilter
まとめ
READMEを読むのは勉強になるなー。
結構コードリーティングの練習にもなったので継続して勉強していきたい。