オンデマンドリソースでiOSアプリを軽くする


よくゲームアプリで新しいシーンに行ったタイミングでダウンロードが始まることがありますが、このような仕組みをAppleが公式に用意してくれましたよ、という「オンデマンドリソース」という仕組みがiOS9から使えるようになったので試してみます。

##オンデマンドリソースの準備
オンデマンドリソースというのは最初から必要なリソースをアプリと一緒にダウンロードするのではなく、必要なタイミングでAppStoreからダウンロードすることで必要最小限のアプリサイズで提供できるようにする仕組みです。
自分で用意したサーバからダウンロードしてくることも可能ですが、ここれは触れません。

まず対象となるリソースを用意しましょう。
なんでもいいですが、ここではアセットカタログを追加して適当な画像を格納しておきます。

![screen1.png](https://qiita-image-store.s3.amazonaws.com/0/77573/89c4a2aa-1766-9813-db49-e44ba881d369.png "screen1.png")


次にプロジェクトを選択してTARGETSを選択し、"Resource Tags"タブを選択します。
「＋」をクリックします。
タグの入力を求められるので適切な名前をつけます。ここでは「Sample1」としました。
追加したSample1の下の「＋」をクリックするとファイル選択のダイアログが表示されるので先ほど追加したアセットカタログを選択します。

![screen2.png](https://qiita-image-store.s3.amazonaws.com/0/77573/1966c892-7e43-3ef8-daf2-795e79fd9eb8.png "screen2.png")


この時、ここで指定したアセットカタログを選択するとアトリビュートインスペクターの一番下の「On Demand Resource Tags」欄に先ほど指定したタグが表示されています。
ここから直接タグ名を記述してもオンデマンドリソースを指定できます。

##必要になったタイミングでリソースをダウンロードする
必要になったタイミングでリソースをダウンロードするために、ここではボタンを用意してタップされたらリソースを取りに行くようにしてみます。
イメージビューとボタンだけからなる簡単なUIを用意しました。

![screen3.png](https://qiita-image-store.s3.amazonaws.com/0/77573/53948542-6e85-2be9-2ba9-71bb40abd9bf.png "screen3.png")


なお、ちゃんとオンデマンドリソースが有効になっていることはプロジェクトのTARGETS、Build SettingsタブのAssets欄にある"Enable On Demand Resources"が「YES」になっていることから確認できます。
もし「NO」に成っていたら「YES」にしておきます。

##コーディング
ボタンのイベントハンドラで以下のように実装します。
サンプルなのでバーッと書いちゃってます。

```
@IBAction func onButton(sender: AnyObject) {
    // 要求するオンデマンドリソースのタグを配列で指定する
    let tags = NSSet(array: ["Sample1"])
    resourceRequest = NSBundleResourceRequest(tags: tags as! Set<String>)

    // デバイスにインストール済みかどうかチェックする
    resourceRequest?.conditionallyBeginAccessingResourcesWithCompletionHandler() {
        resourceAvailable in
        
        // すでにデバイスにリソースがある
        if resourceAvailable {
            dispatch_async(dispatch_get_main_queue()) {
                self.imageView.image = UIImage(named: "mydog")
            }
        } else {
        	// 未インストールなのでダウンロードする

        	// 優先度は0.0-1.0の間で指定できる。
            //self.resourceRequest?.loadingPriority = 0.8

            // ダウンロード実行
            self.resourceRequest?.beginAccessingResourcesWithCompletionHandler() {
                error in
                if error == nil {
                    dispatch_async(dispatch_get_main_queue()) {
                        self.imageView.image = UIImage(named: "mydog")
                    }
                }
            }
        }
    }
}
```
conditionallyBeginAccessingResourcesWithCompletionHandlerでデバイスに指定のリソースが存在するかチェックして、なければbeginAccessingResourcesWithCompletionHandlerを使ってダウンロードしてくる流れになります。
どちらも非同期で動作して結果はコールバックで返ってきます。
また、メインスレッドとは異なるスレッドで動いているのでUIを操作するときはメインスレッドにディスパッチしてやる必要があります。

ダウンロードの進捗を見たい場合は以下のコードを追加します。

```進捗監視追加
self.resourceRequest?.progress.addObserver(self, forKeyPath: "fractionCompleted", options:NSKeyValueObservingOptions.New, context: UnsafeMutablePointer())
```

```進捗管理解除
self.resourceRequest?.progress.removeObserver(self, forKeyPath: "fractionCompleted", context:UnsafeMutablePointer())
```

進捗監視を設定して「observeValueForKeyPath」をオーバライドすると定期的に呼び出されるので進捗を取得できます。

```
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
    if ((object as! NSProgress == resourceRequest!.progress) && (keyPath! == "fractionCompleted")) {
            //  0　から 1
            let progressSoFar = resourceRequest!.progress.fractionCompleted;
            print(progressSoFar)
    }
}
```

##ダウンロードの中断、再開、キャンセル
以下のようにすることでダウンロードの中断、再開、キャンセルを実行できます。

```
resourceRequest.progress.pause()   //中断
resourceRequest.progress.resume()  //再開
resourceRequest.progress.cancel()  //キャンセル
resourceRequest.endAccessingResources() //　リソースが不要になったらこうする
```

サンプルコードは[こちら](https://github.com/kukimo/OnDemandResourceSample)に置いてます。