WWDC Quick Look 💓 By SwiftGGTeam
Discover Apple-Hosted Background Assets

Discover Apple-Hosted Background Assets

元の動画を見る

ハイライト

ゲームや大規模なアプリのリソース配信は常に課題でした。すべてをメインパッケージに詰め込むとサイズが大きくなりすぎます(4GB の制限もあります)。On-Demand Resources を使うと、アプリと一緒に更新する必要があります。Background Assets の Managed モードはこれらの問題を解決します。


主要内容

チュートリアルステージを含むゲームを開発する際、開発者は通常、選択のジレンマに直面します。すべてのリソースをメインパッケージに詰め込むか、ユーザーは数百MB〜数GBのダウンロードを待ってからゲームに入ることになります。多くのプラットフォームには4GBの上限があります。On-Demand Resources を使用する方法もありますが、素材ファイルを1つ更新するだけでアプリ全体を再提出する必要があります。3番目の方法として、URLSession で独自のダウンロードロジックを書くこともできますが、リトライ、レジューム、バージョン調整などを自分で処理する必要があります(01:54)。

WWDC25で提示された新しいソリューションは、Managed Background Assets と Apple ホスティングです。リソースは複数のアセットパックに分割され、各パックには独自のダウンロードポリシーがあります。Essential はアプリのインストールと一緒にダウンロードされ、Prefetch はインストール後にバックグラウンドでダウンロードを継続し、On-demand はコードからリクエストされたときにダウンロードされます(04:00)。システムは既製のダウンローダー拡張を提供し、コードを書かずに統合できます。Apple Developer Program のメンバーは200GBの無料ホスティングスペースを利用でき、アセットパックとアプリビルドは App Store Connect で個別にアップロード、審査、バージョン管理ができます。リソースを更新するために新しいアプリバージョンをリリースする必要はありません(03:36)。チュートリアルステージには Essential + firstInstallation を指定し、新規ユーザーはインストール時に直接取得でき、既存ユーザーがバージョン更新しても重複ダウンロードされません。


詳細

最初のステップはマニフェストを書くことです。xcrun ba-package template で JSON テンプレートを生成し、アセットパック ID、ダウンロードポリシー、プラットフォーム、ファイルセレクタを入力します(08:26)。

{
	"assetPackID": "Tutorial",
	"downloadPolicy": {
		"essential": {
			"installationEventTypes": [
				"firstInstallation",
				"subsequentUpdate"
			]
		}
	},
	"fileSelectors": [
		{ "file": "[Path to File]" },
		{ "directory": "[Path to Directory]" }
	],
	"platforms": ["iOS", "macOS", "tvOS", "visionOS"]
}

重要ポイント:

  • assetPackID: カスタム文字列で、ランタイムコードがこのパックを参照するために使用します。
  • downloadPolicy は3つから1つを選択: essentialprefetchonDemand。最初の2つは installationEventTypes 配列を指定し、onDemand は空のオブジェクト {} を指定します。
  • installationEventTypes でダウンロードタイミングを決定: firstInstallation は初回インストール時のみダウンロード、subsequentUpdate はアップグレード時にもダウンロード。
  • fileSelectors はファイルまたはディレクトリで任意の数を追加可能。パスはソースコードルートからの相対パスです。
  • platforms でこのパックが対応するプラットフォームを明記。複数プラットフォームで同じパックを使用できます。

マニフェストを書いた後、もう一度 ba-package を実行して内容を .aar アーカイブにパッケージ化します(09:53)。

次に Xcode で Background Download テンプレートの extension target を追加します。今年新しく追加されたシステムレベルのダウンローダー拡張で、ほぼコードを書く必要がありません(10:44)。

import BackgroundAssets
import ExtensionFoundation
import StoreKit

@main
struct DownloaderExtension: StoreDownloaderExtension {
	
	func shouldDownload(_ assetPack: AssetPack) -> Bool {
		return true
	}
	
}

重要ポイント:

  • StoreDownloaderExtension は Apple ホスティングシーンのプロトコルで、セルフホスティングでは BackgroundDownloaderExtension を使用します。
  • shouldDownload(_:) はシステムが各新しいパックをスケジュールする前に1回呼び出します。false を返すとダウンロードをスキップでき、互換性フィルタリングに適しています。
  • カスタマイズが不要な場合、shouldDownload(_:) 全体を削除できます。システムのデフォルト実装がすべて処理します。

メインアプリでアセットパックを読み込むコードも短いです(11:39)。

let assetPack = try await AssetPackManager.shared.assetPack(withID: "Tutorial")

// Await status updates for progress information
let statusUpdates = AssetPackManager.shared.statusUpdates(forAssetPackWithID: "Tutorial")
Task {
	for await statusUpdate in statusUpdates {
		// …
  }
}

// Download the asset pack
try await AssetPackManager.shared.ensureLocalAvailability(of: assetPack)

重要ポイント:

  • AssetPackManager.shared はエントリーシングルトンで、すべての操作はここから始まります。
  • assetPack(withID:) でパックのハンドルを取得。ほとんどの場合、ダウンローダー拡張がバックグラウンドで既にダウンロード済みです。
  • statusUpdates(forAssetPackWithID:)AsyncSequence を返し、ループで .downloading(_, progress) などのステータスを取得してUIに表示できます。
  • ensureLocalAvailability(of:) はコアメソッド。既にダウンロード済みなら即座に返り、未ダウンロードならダウンロード完了を待ちます。稀に長く待つ場合があるので、同時にステータスを購読して進捗を表示することをお勧めします。

ダウンロード完了後のファイル読み取りは contents(at:) を使用します(12:41)。

// Read a file into memory
let videoData = try AssetPackManager.shared.contents(at: "Videos/Introduction.m4v")

// Open a file descriptor
let videoDescriptor = try AssetPackManager.shared.descriptor(for: "Videos/Introduction.m4v")
defer {
	do {
		try videoDescriptor.close()
	} catch {
		// …
	}
}

重要ポイント:

  • contents(at:) のパスはソースコードルートからの相対パスで、ba-package 実行時の作業ディレクトリと一致します。システムはすべてのアセットパックを1つの共有名前空間にマージし、ファイル読み取り時にどのパックにあるかを気にする必要がありません。
  • デフォルトでは memory-mapped Data を返し、大ファイルに適しています。メモリを一度に占有しません。
  • descriptor(for:) でファイル記述子を取得して低レベル読み取りを行い、必ず自分で close() する必要があります。defer の使用をお勧めします。
  • 両方のメソッドはオプションの searchingInAssetPackWithID: パラメータを受け取り、検索範囲を単一パックに限定できます。

不要になったパックは remove(assetPackWithID:) を呼び出してスペースを解放し、後で ensureLocalAvailability(of:) で再ダウンロードできます(13:56)。システムは自動的にダウンロード済みパックを最新バージョンに保ちますが、自動削除はしません。ユーザーは設定でアプリがどのくらいのスペースを使用しているかを確認できます。

最後に設定です(14:53)。

<key>BAAppGroupID</key>
<string>group.com.naturelab.thecoast</string>
<key>BAHasManagedAssetPacks</key>
<true/>
<key>BAUsesAppleHosting</key>
<true/>

重要ポイント:

  • BAAppGroupID: メインアプリとダウンローダー拡張は同じ App Group に参加する必要があります。システムはこれで両端を調整します。
  • BAHasManagedAssetPacks: Managed モードを使用すると宣言し、システムがダウンロードと更新を引き継ぐようにします。
  • BAUsesAppleHosting: Apple ホスティング使用時は true に設定。セルフホスティングの場合は BAManifestURL などのキーを別途設定する必要があります。

ローカルテストには ba-serve でモックサーバーを起動します。ダウンロードは HTTPS 経由なので、まずルート CA を作成し、テストデバイスにインストールし、SSL 証明書を署名し、モックサーバーを起動する必要があります。デバイス側の Developer Settings > Development Overrides にモックサーバーのアドレスを入力即可(15:29)。

リリースフローも独立です。アセットパックは Transporter アプリでドラッグ&ドロップしてアップロードするか、App Store Connect REST API を3ステップで使用: まず POST backgroundAssets でパックレコードを作成、次に POST backgroundAssetVersions でバージョンを作成、最後に backgroundAssetUploadFiles.aar をアップロードします(18:47)。同じアセットパックは複数のライブバージョンを持てます: App Store、TestFlight 外部テスト、TestFlight 内部テストそれぞれ1つずつで、サーバーはアプリのソースに応じて対応するバージョンを選択して配信します(05:34)。これはリソースパックを更新すると、古いバージョンのアプリをインストールしているユーザーも自動的に新しいリソースパックに切り替わることを意味するため、新しいパックをリリースする前に必ず後方互換性をテストする必要があります。


重要ポイント

  • 初回インストール体験を Essential pack として切り出す: チュートリアルステージ、最初の画面の素材を1つのパックにまとめ、ダウンロードポリシーを essential + firstInstallation に選択すると、既存ユーザーがアップグレードしても重複ダウンロードされません。新規ユーザーはインストール完了後すぐにプレイでき、リテンションデータが大幅に改善されます。

    • なぜやる価値があるか: ゲームアプリのメインパッケージは1GBから始まり、ユーザーは App Store でダウンロード完了を待つだけで離脱が発生し、ゲームに入ってからロードを待つとさらに離脱します。Essential はこれら2つのダウンロードを App Store のプログレスバーに統合し、ユーザーには1回の待機時間しか感知させません。
    • どう始めるか: xcrun ba-package template を実行してマニフェストテンプレートを取得し、最初の画面素材のディレクトリパスを fileSelectors に入力し、installationEventTypesfirstInstallation のみを残し、Apple ホスティングと組み合わせてそのまま使用。サーバーを構築する必要がありません。
  • DLC を On-demand pack として改造し IAP と連携: 有料チャプター、期間限定イベントマップ、ホリデースキンなどのコンテンツは、ダウンロードポリシーを onDemand に変更し、購入成功後に ensureLocalAvailability(of:) を呼び出してダウンロードをトリガーします。

    • なぜやる価値があるか: DLC はダウンロードしなければユーザーのデバイス容量を消費せず、チャプター完了後に remove(assetPackWithID:) で削除できます。イベントバージョンは新しいパックでプッシュされ、アプリ審査を通す必要がなく、ホリデーキャンペーンのペースをより高速にできます。
    • どう始めるか: 各 DLC を個別のパックとしてパッケージ化し、購入完了フローから pack ID でリソースを取得。進捗表示は statusUpdates(forAssetPackWithID:).downloading ステータスを監視します。
  • オンデバイス ML モデルを Background Assets に移行: CoreML、ベクトルインデックス、埋め込みベクトルテーブルなどの反復するバイナリリソースは、別のパックとして配信し、モデルアップグレードは asset pack 経由で、アプリ経由ではなく行います。

    • なぜやる価値があるか: モデルを変更するたびにアプリ全体をリリースするのは重すぎ、App Store 審査も遅い。asset pack は審査が高速で、ユーザーはアプリをアクティブにアップグレードしなくても新しいモデルを取得できます。
    • どう始めるか: モデルファイルを独立ディレクトリに配置し、パッケージング時に directory セレクタを書いてディレクトリ全体をパッケージ化。descriptor(for:) でファイル記述子を取得して ML フレームワークに渡します。新しいモデルバージョンをリリースする前に、必ず古いバージョンのアプリで回帰テストを行ってください。古いアプリも自動的に新しいリソースに切り替わるためです。
  • On-Demand Resources からの移行: ODR は廃止が発表されており、新規プロジェクトでは使用せず、既存プロジェクトは移行計画を立ててください。

    • なぜやる価値があるか: ODR リソースはアプリビルドに縛られ、画像を1枚更新するだけで再審査が必要。Background Assets はデカップリング後、開発ペースが完全に異なります。
    • どう始めるか: まず ODR タグをビジネス機能で再分割してアセットパックにし、ダウンロードポリシーは元の prefetch/on-demand 動作に対応させ、一度に1つの機能モジュールを移行し、最初にモックサーバーでセルフテストしてから TestFlight に移行します。
  • Apple ホスティングの 200GB を活用: Developer Program メンバーシップには 200GB の無料ホスティングが含まれ、セルフホスティングの CDN 請求を節約できます。

    • なぜやる価値があるか: 自分で CDN を構築すると署名、スロットリング、不正アクセス防止を処理する必要がありますが、Apple ホスティングはこれらすべてを引き継ぎ、プラットフォーム/ビルドに応じたバージョンマッチングも自動的に行います。
    • どう始めるか: Info.plist に BAUsesAppleHosting = true を追加し、ダウンローダー拡張テンプレートで Apple hosting を選択し、.aar を Transporter 経由でドラッグ&ドロップするだけです。

関連セッション

コメント

GitHub Issues · utterances