Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
74
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

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

よくゲームアプリで新しいシーンに行ったタイミングでダウンロードが始まることがありますが、このような仕組みを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() // リソースが不要になったらこうする

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

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
74
Help us understand the problem. What are the problem?