BlogKLabは、多くのスマートフォン向けゲームを開発・提供しています。スマートフォン向けゲームの開発と運用は、Webソーシャルゲームと比べて格段に複雑で、またコンシューマゲーム開発とも異なったノウハウが求められる領域もあります。このブログでは、KLabのゲーム開発・運用の中で培われた様々な技術や挑戦とそのノウハウについて、広く紹介していきます。2016/03/31ダウンロードならバックグラウンドに任せよう今回はiOS7以降におけるバックグラウンドダウンロードの仕組みについて基礎とハマりどころなどを紹介します。バックグラウンドダウンロードというのは、データのダウンロードが始まった後にユーザーが他のアプリを起動したり、端末をスリープさせたりしても、裏でダウンロードをおこない続けることです。データのダウンロードをあまり意識せず、待つ時間を有効に利用できます。ユーザーにとっては非常に優しい機能ですので、是非試してみてください。実際にバックグラウンドダウンロード機能を実装するにあたって、よくはまるところも共有したいと思います。今回はじめてiOSのダウンロード機能に触れる方もいるかもしれませんので、ここでまず簡単なおさらいをします(以下のコードはすべてObjective-Cベースですが、Swiftでも基本的に扱うAPIは同じです)。バックグラウンドダウンロードはiOS7で追加されたNSURLSessionがサポートする機能です。backgroundSessionConfigurationで作ったsessionの利用が前提です。データはNSURLSessionDownloadTaskを通してダウンロードします。NSURLSessionNSURLSessionに関するプログラミングガイド簡単なサンプルや仕様の説明が書いてあります。これを読んでください。※そのページの左側のリンクからいろんなページにとんで、さらに資料を入手できます。Forumsはまった時にここで解決策を探したり、開発チームに質問したりするのがおすすめです。Networking結構前の発表ですが、意外と役に立ちます。(後述)※画面の左下の「Resource」ボタンを押して、PDFをダウンロードするのがおすすめです。ゴールバックグラウンドで約1GBのデータをダウンロードすることです。さあ、やってみましょう。コードを組む前に、まず考えてなければならないことがいくつかあります。ダウンロード対象の1GBのデータをどういうふうに置くかダウンロードのフローをどうするか例外が発生した時にどう対応するか正直、自分は最初にプログラミングガイドしか読んでいなかったので、何となくいけそうな設計を出しました。例外に関しては1zipにはたかが2MBなので、たとえダウンロード失敗したり、ファイルが壊れてしまったりしても、それを捨ててもう一度ダウンロードすればよいという考え上記の案に基づいて実装しましたが、見事にほぼ全部の地雷を踏みました。こちらのフローを説明します。DownloadManagerを定義し、必要なdelegateを継承します。(^)())completionHandlerから呼ばれるアプリがバックグラウンドになった時のみ呼ばれるダウンロードのコールバックを全部処理した後にすぐcompletionHandlerを呼ぶ必要がある一見正しそうに見えますし、実際に実行してみると確かにダウンロードがおこなわれます。*)locationリジュームしたタスクがすべてダウンロード完了するまでこの関数は一度も呼ばれないファイルコピーができなくなり、新しいタスクを生成できなくなる★極端な例として、1GBのデータのうち0.99GBがダウンロードできて、最後にアプリが何かしらの原因でクラッシュしてしまった場合、ダウンロードできた全てのデータを失う可能性があるこの関数の中で新しく作ったタスクは何故かダウンロードが非常に遅い最初に遭遇した現象は、ダウンロードが終わってもこの関数が呼ばれないままずっとハングアップ状態になるというもの。これはOS側のバグかと考えてiOS8.2へアップデートしたところ、ずっと放置したら呼ばれることがわかった。❌バックグラウンドでタスクを作るループではダウンロード速度の面で実用性が乏しい点と、ダウンロード済みデータを回収する仕組みを改めて考えざるを得ない場面になりました。アプリが途中でクラッシュした場合、次回は未ダウンロードのパッケージのみをダウンロードする必要があります。*)locationが呼ばれるので、ここで未完成のタスクを削除する(必要に応じて処理を追加してください)※ホームボタンをダブルクリックし、アプリを意図的に終了した場合は途中データの回収をしないパッケージ10個でテストした限りでは特に問題ありませんでした。問題が発生しましたパッケージ数を500個まで増やしてテストした場合、-(void)onAppWillEnterBackgroundの処理は制限時間内に終わらず、アプリがOSに落とされる。(Xcodeに繋いで実行している場合は警告が出力されるだけで、落とされない)むりやりタスク生成処理をbackgroundTaskに回して落ちないようにしたら、今度はコールバックがおかしくなって失敗その後に調べた結果、ここの返答にAppleのデベロッパーがこう書いています。「バックグラウンドセッションは相対的に少数の大きいサイズのファイルのための設計です。Networkingからダウンロードした707_whats_new_in_foundation_networking.pdfの中身を読むと、こんなアンチパターンと推奨の使い方が書いてあります。20zipダウンロードのフローはダウンロードするファイルの一覧をリストに格納アプリがフォアグランドになった時にNSURLSessionDownloadTaskを二つずつ作って、前のダウンロードが終わった時に新たなNSURLSessionDownloadTaskを作るという循環でリストをどんどん消化アプリがバックグラウンドに行く前に、待ち行列にあるすべてのURLからタスクを大量に作って、全部リジューム例外に関してはダウンロードの進捗をsqliteに書き込んで、すでにダウンロード済みのものはもう一度ダウンロードさせない起動するたびに前回残ったタスクの状況を調べて、ダウンロード完了状態のものを全部回収し、途中までダウンロードしたものはキャンセルまとめバックグラウンドダウンロードは実に便利かつユーザーに優しい機能なので、多少実装・検証コストかかっても入れたほうがいいと思います。実は今回一つの課題が残っています。クラッシュから再開した時に途中までのタスクをキャンセルせずに途中からダウンロードするいわばRangeDownloadの対応です。いつか対応しようと思っています。