自分のアプリを「ユーザーとして」楽しむための専用ターゲット
自分が作ったアプリを、開発者としてではなく一人のユーザーとして使いたい。開発ビルドだとデバッグログが流れるし、テスト用の設定が残っていて純粋に楽しめない。PlayTimeの場合、ボス協力機能の動作確認中にうっかりテストデータを上書きしたりする。
そこで開発と完全に分離した自分専用ターゲットを作ることにした。別のBundle IDでTestFlight1配信すれば、開発ビルドとは独立した「ユーザー版」を自分の端末に入れられる。
やるべきことは以下の通り。
- Xcode GUIでターゲットをDuplicate(メインアプリ・Watch App・Watch Widget Extensionの3つ全て)
- Bundle IDを専用のものに変更
- App Groupを新ターゲットにも登録
- ハードコードされたBundle ID・App Group名をplist/Bundle APIから動的取得に変更
- アイコンにバナー等を追加して開発版と視覚的に区別
必要なこと
それぞれ詳しく見ていく。
1. ターゲット複製 — GUI Duplicateが正解
Xcodeでターゲットを右クリック → 「Duplicate」。これだけで以下が全てコピーされる。
- SPM2パッケージ依存
- Embed Frameworks3 Build Phase(CodeSignOnCopy4フラグ含む)
- Capabilities(Push Notifications、Keychain Sharing等)
- Build Settings、Build Phases全て
Watch Appとその拡張も含めて、依存関係ごと正しく複製される。PlayTimeForMeの場合、メインアプリ・Watch App・Watch Widget Extensionの3ターゲット全てをDuplicateする必要がある。
💡 手順: (1) Xcode GUIでDuplicate → (2) Bundle ID変更 → (3) Display Name変更 → (4) Info.plist編集。構造変更はGUI、テキスト変更はエージェントに任せるのが安全。
2. Bundle ID分離
専用ターゲットには本番とは異なるBundle IDを設定する。
- 本番:
jp.po-miyasaka.PlayTime - 専用:
jp.po-miyasaka.PlayTimeForMe
Bundle IDが別なので、TestFlightで本番版と専用版を同じ端末に共存できる。App Store Connectにも別アプリとして登録される。
3. App Group設定
PlayTimeではWidget Extensionとのデータ共有にApp Group5を使っている。専用ターゲットでもWidgetを動かすなら、App Groupの設定が必要。
- 専用ターゲットのentitlementsにApp Groupを登録
- Developer PortalでApp Group IDを作成・紐づけ
これを忘れるとRealmのDBやUserDefaults6の共有が機能しない。データは保存されるように見えても、再起動で消える(App Groupコンテナにアクセスできず、メモリのみで動作するため)。
4. ハードコード値の動的取得
コード内にBundle IDやApp Group名をハードコードしていると、ターゲットを変えた途端に壊れる。
// NG: ハードコード
static let groupID = "group.jp.po-miyasaka.PlayTime"
// OK: Info.plistから動的取得
static var groupID: String {
Bundle.main.object(forInfoDictionaryKey: "AppGroupIdentifier") as? String
?? "group.jp.po-miyasaka.PlayTime"
}
Bundle ID、App Group名、API Key等、ターゲット固有の値はInfo.plist7やBundle.mainのAPIから取得する設計にしておく。ターゲットごとにplistの値を変えるだけで対応できるようになる。
5. アプリアイコンの区別
Bundle IDを分けると同じ端末に本番版と専用版が共存する。ただしアイコンが同じだと、ホーム画面でどちらを開いているか分からない。
PlayTimeForMeでは、アイコンに青い「ForMe」バナーを追加して視覚的に区別した。Assets.xcassets8にAppIcon-ForMeとしてForMeターゲット専用のアイコンセットを配置し、ターゲットのBuild SettingsでApp Icon Assetを切り替える。
| 開発版(PlayTime) | ForMe版(PlayTimeForMe) |
|---|---|
ForMe版は下部に青い「ForMe」バナーが入っている。ホーム画面で一目でどちらか分かる。
技術的な分離(Bundle ID、App Group等)だけでなく、日常的に使う上で混乱しないUXレベルの工夫も必要だった。
つまずいたポイント
実際にはすんなりいかなかった。事実だけ記録しておく。
1. pbxproj9テキスト編集によるdyldクラッシュ
AIエージェントにpbxprojを直接テキスト編集させてターゲットを作成した。ビルドは通ったが、Embed Frameworks3 Build Phaseのコピーが漏れていてdyld: Library not loaded: @rpath/RealmSwift.framework/RealmSwift10で起動クラッシュ。pbxprojは数千行の独自フォーマットで、SPM依存・フレームワークリンク・Build Phases・Build Settings等が複雑に絡み合っている。AIがテキスト編集で全ての依存関係を正しくコピーするのは困難。GUI Duplicateならこれら全てが自動でコピーされるため、この問題は起きない。
2. App Group名ハードコードによるデータ消失
RealmService.swiftにApp Group名がハードコードされていた。専用ターゲットにはそのApp Groupが未登録だったため、Realmがメモリのみで動作し、再起動でデータが全消失。entitlementsへのApp Group登録と、plistからの動的取得に修正。
3. フォールバック修正の罠
上記2の修正としてエージェントが出してきたのは「App Groupが使えなければDocumentsにフォールバック」。動作はするが、Widget連携が壊れる・ForMe版だけ劣化した動作になるなど本質的に間違い。正しくはentitlementsにApp Groupを登録し、ハードコードをplist動的取得に変更すること。
まとめ
開発用ビルドと分離した自分専用ターゲットを作るために必要なこと。
| 作業 | ポイント |
|---|---|
| ターゲット複製 | Xcode GUI Duplicateで行う。メインアプリ+Watch App+Widget Extensionの全ターゲットが対象 |
| Bundle ID | 本番と別のIDを設定。TestFlightで共存可能に |
| App Group | 専用ターゲットのentitlementsにも登録。Widget連携に必須 |
| ハードコード排除 | Bundle ID・App Group名等はplist/Bundle APIから動的取得 |
| アイコン区別 | ForMe版に専用バナーを追加。ホーム画面で一目で判別可能に |
プロジェクト構造の変更(ターゲット追加・SPM操作・Capabilities)はGUIで行い、テキストレベルの設定変更(Bundle ID・plist編集等)はエージェントに任せる。この使い分けが安定する。
Footnotes
-
TestFlight — Appleが提供するベータテスト配信サービス。App Store Connectにアップロードしたビルドを、招待したテスターの端末にOTA配信できる。 ↩
-
SPM (Swift Package Manager) — Apple公式のパッケージ管理ツール。Xcode 11以降で統合されており、Package.swiftで依存関係を定義する。 ↩
-
Embed Frameworks Build Phase — Xcodeのビルドフェーズの1つ。動的フレームワークをアプリバンドルの
Frameworks/ディレクトリにコピーする。これがないと実行時にdyldがフレームワークを見つけられない。 ↩ ↩2 -
CodeSignOnCopy — Embed時にフレームワークを再署名するオプション。アプリ本体の署名と一致させるために必要。 ↩
-
App Group — 同じ開発者の複数アプリ・Extension間でデータを共有するためのiOS/macOSの仕組み。共有コンテナとUserDefaultsの共有に使う。 ↩
-
UserDefaults — iOS/macOSの軽量キーバリューストア。
UserDefaults(suiteName:)でApp Group経由の共有UserDefaultsにアクセスできる。 ↩ -
Info.plist — iOSアプリのメタデータファイル。Bundle ID、バージョン、権限宣言等に加え、カスタムキーを追加してターゲット固有の値を格納できる。 ↩
-
Assets.xcassets — Xcodeのアセットカタログ。アプリアイコン、画像リソース等を管理する。ターゲットごとに異なるApp Iconセットを指定できる。 ↩
-
pbxproj —
project.pbxprojファイル。Xcodeプロジェクトの中核。ターゲット、ビルド設定、ファイル参照、Build Phase等の全情報を独自フォーマットで格納している。 ↩ -
@rpath — 実行時検索パス。dyldが動的ライブラリを探す際のベースパス。iOSアプリでは
@executable_path/Frameworksが設定される。 ↩