WatchKitでWatchに画像をキャッシュする


WatchKitでは明示的にWatchに画像をキャッシュする方法が用意されています。
使用方法と、なぜ画像のキャッシュが必要なのかをまとめてみました。

`2014/12/15[修正]Xcode6.2β2でキャッシュに追加するメソッドに戻り値が追加されたので一部記述を修正しました。`

## 画像をキャッシュする方法

`WKInterfaceDeivce`クラスの下記メソッドがキャッシュ関連のメソッドです。

```swift
func addCachedImage(_ image: UIImage!, name name: String!) -> Bool
func addCachedImageWithData(_ imageData: NSData!, name name: String!) -> Bool
func removeCachedImageWithName(_ name: String!)
func removeAllCachedImages()
```

キャッシュは最大 20 MB で、超過した場合は古い物から順に消されるとのこと。
なお、シミュレータ上ではこの制限を超過しても消されることはありませんでした。

### WKInterfaceDeviceクラスとは？
Watchの情報をカプセル化して提供してくれるクラスです。
キャッシュ関連のメソッドの他、画面サイズやロケール情報などを提供してくれます。
このクラスは、サブクラスを作ったりインスタンスを作ってはいけません。

`currentDevice`クラスメソッドを用いてインスタンスを取得します。

```swift
let device = WKInterfaceDevice.currentDevice()
```

### 画像をキャッシュしてみる
```swift
// 新しくキャッシュする
let imageDog = UIImage(named: "dog")  // WatchKit ExtensionのアセットからUIImageを作る
device.addCachedImage(imageDog, name: "bowwow") // キャッシュする
```
既に同名のキャッシュが存在する場合、更新は行われず古いキャッシュがそのまま残ります。

### キャッシュを更新してみる
キャッシュを更新する場合、先に古いキャッシュを消す必要があります。

```swift
// キャッシュを更新する
device.removeCachedImageWithName("bowwow")      // 古いキャッシュを消す
device.addCachedImage(imageDog, name: "bowwow") // キャッシュする
```

### キャッシュした画像を使ってみる
`WKInterfaceImage`クラスの`setImageNamed(_ imageName: String!)`メソッドでキャッシュした画像が使われます。
なお、このメソッドはWatch Appに含めたアセットを使う場合にも使います。
アセットとキャッシュ、どちらにも存在しない場合は`setImage(nil)`した時と同じ挙動になるようです。

```swift
@IBOutlet weak var image: WKInterfaceImage!
image.setImageNamed("bowwow")
```

### キャッシュとアセットの関係について
前述の通り、`WKInterfaceImage`クラスの`setImageNamed(_ imageName: String!)`メソッドは
アセットを使う時とキャッシュを使う時のどちらでも使える共通のインターフェースとなっています。
そのためかアセットとして存在している名前をキャッシュ名として使用することはできなくなっています。

例えば"cat"という名前のアセットが存在する場合、"cat"という名前のキャッシュは作成できません。
またこの時、"cat"という名前のキャッシュを削除しようとしても(当然ですが)何も効果がありません。
~~エラーハンドリングできないので注意が必要です。~~
→Xcode6.2βから、キャッシュ追加時にBool値で判定できるようになりました。

### キャッシュの生存期間（不明）
キャッシュした画像がアプリ終了後も残るのか、
公開されているドキュメント内で明確な記述を見つけられませんでした。

リファレンスの一文を見るとなんとなく永続化してくれそうな気もします。
>Each app receives 20 MB of cache storage. -[WatchKit Framework Reference](https://developer.apple.com/library/prerelease/ios/documentation/WatchKit/Reference/WKInterfaceDevice_class/index.html#//apple_ref/occ/cl/WKInterfaceDevice)

## なぜキャッシュが必要なのか
なぜこのようなキャッシュが必要なんでしょうか？ 理由は下の図を見ると分かります。

> [WatchKit Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1)から引用
> ![app_communication_2x.png](https://qiita-image-store.s3.amazonaws.com/0/45742/40bfbe7c-d7f1-c3e8-ad68-70485abb0a65.png "app_communication_2x.png")

WatchKit Extension(コード/リソース)はiPhone側にあります。そのため、リソースは
Watch Appが保持しているリソースを除いてiPhoneからWatchKit経由でWatchに転送されます。
この時にWatchにキャッシュしてしまえば、1回目以後転送が発生しないというわけです。

iPhoneから何度もリソースを転送するより、
Watchにキャッシュしておいてそこから取り出す方が速いし電力消費も少ないと考えられます。
だからキャッシュが必要なんだと思います。

### キャッシュが必要な画像、不要な画像
動的な画像で2回以上使う物は可能な限りキャッシュする。
静的な画像はWatch Appに含めるようにしてキャッシュしない。(する必要がない)

後は、アプリの仕様と実機の特性にあわせて最適化することになりそうです。

## まとめ
キャッシュは実機上で動かすまで効果を感じられないかもしれません。
それでも、もし早期のアプリリリースを目指すなら覚えておきたいポイントだと思います。

参考：
[WatchKit Programming Guide](https://developer.apple.com/library/prerelease/ios/documentation/General/Conceptual/WatchKitProgrammingGuide/DesigningaWatchKitApp.html#//apple_ref/doc/uid/TP40014969-CH3-SW1)
[WatchKit Framework Reference > WKInterfaceDevice Class Reference](https://developer.apple.com/library/prerelease/ios/documentation/WatchKit/Reference/WKInterfaceDevice_class/index.html#//apple_ref/occ/cl/WKInterfaceDevice)