iOS
UIImage
Cache
Swift
AlamofireImage

iOSの画像をキャッシュする方法とAlamofireImageの解説

More than 1 year has passed since last update.


目的

画像をキャッシュするための方法がわからず、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を使って画像をダウンロードした際はNSURLCacheAutoPurgingImageCacheがともに使われる


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を読むのは勉強になるなー。

結構コードリーティングの練習にもなったので継続して勉強していきたい。