ハイライト
iOS 17 の Background Assets には Essential Downloads が追加され、アプリは App Store のインストール中に必須リソースをダウンロードでき、ユーザーは初回起動時に待たされません。さらに
xcrun backgroundassets-debugで Extension の各エントリーポイントをシミュレートできます。
主要内容
ユーザーが最も嫌がるのは、アプリを開いた直後に長いダウンロード進捗バーを見ることです。ゲームのステージ、動画講座、雑誌コンテンツ——大容量リソースを起動後にダウンロードすると体験が悪化します。
(01:05)Background Assets フレームワーク(iOS 16.1 導入)は App Extension 経由でアプリが動いていない間にリソースをダウンロードします。今年は Essential Downloads が追加され、重要なリソースを App Store インストール中に完了できます。
基本アーキテクチャ
(01:20)Background Assets は 2 つの部分で構成されます:
- Background Assets フレームワーク:アプリ内でダウンロードをスケジュール
- App Extension:アプリ未実行時にダウンロードロジックを実行
Extension は次の 3 つのタイミングで起動されます:アプリインストール時、アプリ更新時、バックグラウンドの定期チェック時。
Essential Downloads
(10:35)Essential Downloads は今年の中核機能です。essential とマークされたリソースはアプリインストール中にダウンロードされ、App Store のダウンロード進捗と統合表示されます。必須ダウンロードが完了するまでユーザーはアプリを起動できません。
ゲームの最初のステージ、アプリのコアコンテンツなど、初回起動時に必須のリソースに適しています。
実行時制限
(04:42)Extension には厳しいリソース制限があります:
- メモリ:数 MB 程度。超過すると終了
- 実行時間:デフォルトで 1 日数分。アプリの使用例頻度に応じて動的調整
- 低電力モードやバックグラウンド更新オフ時は Extension は動作しない
デバッグツール
(29:38)xcrun backgroundassets-debug で Extension の各エントリーポイントを、実際のインストールやバックグラウンドイベントを待たずにシミュレートできます。
詳細
Info.plist の設定
(16:00)アプリの Info.plist に必須キーを追加:
<key>BAManifestURL</key>
<string>https://your-cdn.com/manifest.json</string>
<key>BAInitialDownloadRestrictions</key>
<dict>
<key>BADownloadAllowance</key>
<integer>104857600</integer>
<key>BAEssentialDownloadAllowance</key>
<integer>52428800</integer>
<key>BADownloadDomainAllowList</key>
<array>
<string>your-cdn.com</string>
</array>
</dict>
<key>BAMaxInstallSize</key>
<integer>209715200</integer>
<key>BAEssentialMaxInstallSize</key>
<integer>104857600</integer>
キーポイント:
BAManifestURL:マニフェストファイルの URLBADownloadAllowance:非必須リソースの総ダウンロードサイズ上限(バイト)BAEssentialDownloadAllowance:必須リソースの総ダウンロードサイズ上限(バイト)BAEssentialMaxInstallSize:必須リソースインストール後のディスク使用例量上限BAMaxInstallSize:非必須リソースインストール後のディスク使用例量上限BADownloadDomainAllowList:初回起動前にダウンロードを許可するドメインのホワイトリスト
Extension の実装
(24:46)Background Assets Extension を作成:
import BackgroundAssets
class DownloadExtension: NSObject, BADownloaderExtension {
func downloads(for request: BAContentRequest,
manifestURL: URL,
extensionInfo: BAAppExtensionInfo) -> Set<BADownload> {
// マニフェストファイルを解析
guard let manifestData = try? Data(contentsOf: manifestURL),
let manifest = try? JSONDecoder().decode(Manifest.self, from: manifestData) else {
return []
}
// マニフェストを App Group に保存
saveManifestToAppGroup(manifest)
var downloads: Set<BADownload> = []
for session in manifest.sessions where !session.isDownloaded {
let download = BAURLDownload(
identifier: session.id,
request: URLRequest(url: session.videoURL),
essential: session.isEssential,
fileSize: session.fileSize,
applicationGroupIdentifier: "group.com.yourapp",
priority: .default
)
downloads.insert(download)
}
return downloads
}
func backgroundDownload(_ download: BADownload,
didFinishDownloadingTo fileURL: URL) {
// withExclusiveControl で排他的アクセスを保証
BADownloadManager.shared.withExclusiveControl { control in
guard let control = control else { return }
// 一時場所から最終場所へファイルを移動
do {
let finalURL = self.finalURL(for: download)
try FileManager.default.moveItem(at: fileURL, to: finalURL)
// ローカル状態を更新
self.markSessionAsDownloaded(download.identifier)
} catch {
print("Failed to move file: \(error)")
}
}
}
func backgroundDownload(_ download: BADownload, failedWithError error: Error) {
// 必須ダウンロードが失敗した場合は、非必須として再度キューに入れる
if let urlDownload = download as? BAURLDownload, urlDownload.isEssential {
let nonEssentialDownload = urlDownload.removingEssential()
try? BADownloadManager.shared.scheduleDownload(nonEssentialDownload)
}
}
}
キーポイント:
downloads(for:manifestURL:extensionInfo:)はダウンロード対象リソースの集合を返す- 必須ダウンロードはインストール/更新イベント中にのみスケジュール可能
withExclusiveControlでアプリと Extension の同時ファイル操作を防止- ダウンロード完了後すぐに最終位置へ移動
- 必須ダウンロード失敗時は
removingEssential()で非必須として再キュー
アプリ側の統合
(19:31)アプリでダウンロードを管理:
import BackgroundAssets
class SessionManager: NSObject, BADownloadManagerDelegate {
let downloadManager = BADownloadManager.shared
override init() {
super.init()
downloadManager.delegate = self
}
func startDownload(for session: Session) {
downloadManager.withExclusiveControl { control in
guard let control = control else { return }
// すでにダウンロードキューにあるか確認
if let existingDownload = self.downloadManager.currentDownloads
.first(where: { $0.identifier == session.id }) {
// 存在する場合はフォアグラウンドに昇格してダウンロードを高速化
self.downloadManager.startForegroundDownload(existingDownload)
} else {
// 新しいダウンロードを作成
let download = BAURLDownload(
identifier: session.id,
request: URLRequest(url: session.videoURL),
essential: false,
fileSize: session.fileSize,
applicationGroupIdentifier: "group.com.yourapp",
priority: .default
)
try? self.downloadManager.scheduleDownload(download)
self.downloadManager.startForegroundDownload(download)
}
}
}
// MARK: - BADownloadManagerDelegate
func downloadManager(_ manager: BADownloadManager,
downloadDidProgress download: BADownload) {
updateProgress(for: download.identifier, progress: download.progress)
}
func downloadManager(_ manager: BADownloadManager,
downloadDidFinish download: BADownload) {
// ファイルは Extension 内で最終場所へ移動済み
markSessionAsDownloaded(download.identifier)
}
func downloadManager(_ manager: BADownloadManager,
downloadDidFail download: BADownload,
withError error: Error) {
print("Download failed: \(error)")
}
}
キーポイント:
BADownloadManagerはシングルトンでシステムスケジューラに直接接続withExclusiveControlで Extension との排他制御を保証startForegroundDownloadでバックグラウンドダウンロードをフォアグラウンドに昇格し高速化- フォアグラウンド昇格はダウンロードを再開せず、中断位置から再開
- フォアグラウンドダウンロードはネットワーク問題で即失敗(バックグラウンドのようにリトライしない)
Extension のデバッグ
(29:38)backgroundassets-debug でエントリーポイントをシミュレート:
# 接続済みデバイスを一覧表示
xcrun backgroundassets-debug list-devices
# アプリインストールイベントをシミュレート
xcrun backgroundassets-debug simulate app-install \
--device <DEVICE_UUID> \
--bundle-id com.yourapp
# アプリ更新イベントをシミュレート
xcrun backgroundassets-debug simulate app-update \
--device <DEVICE_UUID> \
--bundle-id com.yourapp
# バックグラウンドの定期チェックをシミュレート
xcrun backgroundassets-debug simulate periodic \
--device <DEVICE_UUID> \
--bundle-id com.yourapp
キーポイント:
- デバイスは開発者モードが有効である必要がある
- Bluetooth や Wi-Fi 経由でコマンド送信可能。USB 不要
- シミュレートイベントは Extension の実行時間制限をリセット
man backgroundassets-debugで完全なドキュメントを確認
ファイル管理のベストプラクティス
(09:44)ダウンロードファイルはデフォルトでパージ可能(purgeable)としてマーク:
// ダウンロードしたファイルを Caches ディレクトリに置く
let cachesURL = FileManager.default.urls(for: .cachesDirectory,
in: .userDomainMask).first!
let finalURL = cachesURL.appendingPathComponent("downloads/\(identifier)")
// コピーではなく move 操作を使う
try FileManager.default.moveItem(at: tempURL, to: finalURL)
キーポイント:
- ダウンロードファイルは Caches ディレクトリに配置。容量不足時にシステムが削除可能
copyItemではなくmoveItemを使い、パージ可能属性を維持- 変更や展開後のファイルはシステム追跡外となり、バックアップ容量を消費
- アプリ起動時にファイル存在を確認し、なければ再ダウンロード
Essential Download のライフサイクル
(11:21)必須ダウンロードのライフサイクル:
- ユーザーが App Store からアプリのインストールを要求
- システムが Info.plist の essential 設定を読み取り
- アプリインストール完了後、システムが Extension を起動
- Extension が必須と非必須のダウンロードの混合集合を返す
- 必須ダウンロードが即座に開始され、アプリインストール進捗と統合表示
- 必須ダウンロード完了後、アプリが起動可能に
- 非必須ダウンロードはバックグラウンドで継続
キーポイント:
- 必須ダウンロードのファイルサイズは正確である必要がある。不正確だと失敗
- ユーザーは App Store 設定でアプリ内コンテンツダウンロードを無効化可能
- アプリは必須リソース未完了のケースに対応する必要がある
- サーバーは HTTP Range リクエストをサポートし、レジューム可能にする
重要ポイント
1. コンテンツ型アプリで「開いてすぐ使える」体験を実現
やること:アプリのコアコンテンツ(ニュースアプリのホームデータ、教育アプリの第 1 レッスン、ゲームの最初の数ステージ)を Essential Downloads にマークし、初回起動ですぐ使えるようにする。
価値:App Store からダウンロード後にコンテンツ待ちが発生すると離脱率が高い。Essential Downloads でコンテンツをアプリと同時に届けられる。
始め方:Info.plist で BAEssentialDownloadAllowance と BAEssentialMaxInstallSize を設定し、Extension の downloads(for:) でコアリソースの essential を true に。
2. スマートなリソースプリロードシステムを構築
やること:バックグラウンドの定期チェックで、ユーザーがアプリを使っていない間に新コンテンツをプリロード。例:Wi-Fi 時にポッドキャストアプリが新エピソードを自動ダウンロード。
価値:Background Assets は使用例習慣に基づき Extension 実行をスマートにスケジュール。よく使うアプリはより多くの実行時間を得る。
始め方:Extension でマニフェストを解析し、サーバー最新とローカル済みを比較して更新用 BADownload 集合を返す。BADownloadManager.currentDownloads で重複スケジュールを回避。
3. ゲームアプリでステージのストリーミング読み込みを実装
やること:ステージを必須(最初の 3 ステージ)と任意(以降)に分け、必須はインストール時、以降はバックグラウンドでプリロード。
価値:ゲームアセットは巨大で、IPA に全部入れると App Store 制限を超える。Background Assets でステージをオンデマンドダウンロード可能。
始め方:各ステージのファイルサイズと URL を記録するリソースマニフェストを設計。初期ステージを essential、残りを非必須に。Extension でユーザー進行に応じて次に使うステージを優先ダウンロード。
4. オフラインファーストの動画学習アプリを実装
やること:オフライン視聴したい動画をユーザーがマークし、Background Assets がバックグラウンドでダウンロード、完了時に通知。
価値:Wi-Fi 環境でマーク、バックグラウンドで自動ダウンロード、地下鉄や飛行機で既にコンテンツが準備完了。
始め方:アプリ内で「ダウンロード待ち」リストを維持し、App Group で Extension と共有。Extension がバックグラウンド実行時にリストを読み取りダウンロードをスケジュール。起動時に withExclusiveControl で状態確認し UI 更新。
関連セッション
- Create a more responsive camera experience — iOS 17 カメラ応答性 API:遅延処理、ゼロシャッターラグ、レスポンシブキャプチャ
- Support external cameras in your iPadOS app — iPadOS アプリで Apple Studio Display などの外部カメラを使用例
- What’s new in the Photos picker — 埋め込みと連続選択を含む Photos picker の新機能
- Meet SwiftUI for spatial computing — 空間コンピューティングプラットフォームにおける SwiftUI の新機能
コメント
GitHub Issues · utterances