Help us understand the problem. What is going on with this article?

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

More than 3 years have 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を読むのは勉強になるなー。
結構コードリーティングの練習にもなったので継続して勉強していきたい。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした