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

# 目的
画像をキャッシュするための方法がわからず、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)

```swift
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を超えた時にキャッシュの画像を削除した後のキャッシュサイズの上限

```swift
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を考えなくていいの、こちらのほうが使い勝手がいいと思う。

```swift
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
```swift
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

```swift
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認証がある場合につかう

```swift
let downloader = ImageDownloader()
downloader.addAuthentication(user: "username", password: "password")
```

### Download Prioritization
ImageDownloaderが画像をダウンロードするときのキューの管理方法を指定する。
- FIFO
- LIFO

### Image Caching
ImageDownloaderを使って画像をダウンロードした際は`NSURLCache`と`AutoPurgingImageCache`がともに使われる

#### NSURLCache
  NSURLCacheによるキャッシュを使いたくない場合は

  ```swift
  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

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