76
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Posted at

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

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

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

screen1.png

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

screen2.png

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

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

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() // リソースが不要になったらこうする

サンプルコードはこちらに置いてます。

76
75
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
76
75

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?