TestFlightへ新しいビルドをアップロードした直後に、App Store ConnectからITMS-90683「Info.plist に NSBluetoothAlwaysUsageDescription が無い」という警告が届くことがあります。Bluetoothを使っていない認識でも発生する理由と、最短で警告を解消する手順、原因になったNuGet/SDKの追い方までまとめます。
症状:ITMS-90683「Missing purpose string in Info.plist」とは
ITMS-90683は、App Store Connect(TestFlightを含む)へアップロードしたアプリに対して行われる静的チェックで、プライバシーに関わるAPIを利用する可能性があるのに、Info.plistに「利用目的の説明文(Purpose String)」が用意されていないときに通知される警告です。
今回のケースでは、Bluetoothに関する説明文であるNSBluetoothAlwaysUsageDescriptionが無いことを指摘されます。これはiOSがユーザーへ表示する許可ダイアログの文言として使われ、Bluetoothアクセスが発生し得るアプリでは必須になります(実行時に許可を求める可能性があるため)。
| 項目 | 内容 | 押さえるポイント |
|---|---|---|
| 警告コード | ITMS-90683 | 「プライバシー利用目的(Purpose String)が不足」系の警告 |
| 今回要求されるキー | NSBluetoothAlwaysUsageDescription | Bluetoothを使う理由を、ユーザーが理解できる文章で書く |
| 発生タイミング | TestFlight/審査用アップロード直後 | アプリを実行していなくても、バイナリ解析で検知される |
| よくある誤解 | 「自分はBluetoothを使っていないのにおかしい」 | 自前コードで未使用でも、依存SDKが参照しているだけで対象になる |
| 放置した場合 | 将来の提出・審査で指摘や差し戻しの原因になり得る | 警告でも早めに潰すのが安全 |
Bluetoothを使っていないのに警告が出る「典型パターン」
結論から言うと、App Store Connectのチェックは「アプリがBluetooth機能を実際に使ったか」ではなく、「BluetoothにアクセスできるAPIやフレームワークを参照(リンク)しているか」を見ています。つまり、開発者が意図していなくても次のような状況で検知されることがあります。
外部SDK(NuGet/サードパーティ)がiOSのCoreBluetoothを参照している
.NET MAUI / Xamarin.iOS などで開発している場合、NuGetパッケージやバインディングされたネイティブSDKが内部でiOSのCoreBluetooth.frameworkを参照していることがあります。パッケージ名や公開APIに「Bluetooth」という単語が出ていなくても、内部実装で参照していれば静的チェックに引っ掛かります。
- 分析・計測・広告系SDKが「近接通信」周りの機能を内包している
- デバイス連携・周辺機器連携のためのSDKを入れていて、Bluetooth関連クラスが含まれている
- 汎用ユーティリティ(端末情報・ネットワーク・連携)に見えるSDKが、iOS側でオプション機能としてBluetoothを含んでいる
たとえば依存関係の確認では、Visual StudioのNuGetマネージャーでDependencies(依存関係)を辿り、どのパッケージがどのパッケージを引っ張っているかを順に追います。目立つ名前が見当たらなくても、内部でネイティブ参照を持つケースがあるため、後述の「ビルドログ」「バイナリ確認」までセットで見るのが確実です。
リンカー設定「Don’t Link(リンクしない)」が検知を助長することがある
「アップロード前にリンカーをDon’t Linkに変えた」という状況は、ITMS-90683に限らずプライバシー系の警告が出やすくなる典型的な要因です。
リンカー(Linker/Trimmer)の役割は、未使用コードや未使用参照を削り、アプリサイズを小さくしつつ不要なフレームワーク参照を落とすことです。ところがDon’t Linkにすると未使用でも残るコードが増えるため、結果として「Bluetooth関連APIを参照している」扱いになりやすくなります。
「参照しているだけ」で要件になるのがiOSプライバシー文言の特徴
iOSでは、カメラ・位置情報・マイク・Bluetoothなど、ユーザーのプライバシーに関わる機能にアクセスする場合、事前にInfo.plistへ目的を明示する設計になっています。これは「ユーザーに説明できないアクセスをしない」ための仕組みで、Appleの審査・プラットフォーム仕様の両面から要求されます。
最短の解決:Info.plistにNSBluetoothAlwaysUsageDescriptionを追加する
原因の切り分けに時間をかけられない場合、まずはNSBluetoothAlwaysUsageDescriptionをInfo.plistへ追加して再アップロードするのが最短ルートです。実際、この対応だけでITMS-90683が出なくなるケースは多く、手戻りを最小化できます。
追加するXML(例)
Info.plistの<dict>内に、次のキーと説明文を追加します。説明文はアプリの実態に合わせて調整してください。
<key>NSBluetoothAlwaysUsageDescription</key>
<string>(例)周辺機器と接続するためにBluetoothを使用します。</string>
.NET MAUI / Xamarin.iOS での編集場所の目安
- .NET MAUI:通常は Platforms/iOS/Info.plist
- Xamarin.iOS(単体 iOS プロジェクト):iOSプロジェクト直下の Info.plist
- 複数ターゲット構成:ビルド構成(Debug/Release)やターゲットフレームワークごとに、最終的にどのInfo.plistが採用されるか確認
「追加したのに警告が消えない」場合は、編集したInfo.plistが実際のビルドに取り込まれていないケースがあるため、後述の確認手順を試してください。
説明文の作り方:審査とユーザー体験の両方を意識する
NSBluetoothAlwaysUsageDescriptionの文言は、将来アプリがBluetoothアクセスを行った際にユーザーへ表示され得ます。審査対策としても重要ですが、ユーザー体験にも直結するため、次のポイントを意識すると安全です。
- 「何のために」必要かを具体的に書く(周辺機器・センサー・デバイス連携など)
- 曖昧な表現だけ(「アプリの品質向上のため」など)は避ける
- 本当にBluetooth機能を提供していないなら、依存SDKの見直しも検討する(後述)
| 想定する利用シーン | 文言例 | 補足 |
|---|---|---|
| 周辺機器と接続する機能がある | 周辺機器と接続するためにBluetoothを使用します。 | 最もシンプルで誤解が少ない |
| 端末近くの機器を検出して連携する | 近くの機器を検出して連携するためにBluetoothを使用します。 | 「検出」「連携」など具体語を入れると伝わりやすい |
| 現状はBluetooth機能を提供していないが、SDK都合でキーが必要 | 一部の外部ライブラリが端末機能を参照するため、必要に応じてBluetoothの利用目的を表示する場合があります。 | 「使っていないのに許可を求める」誤解を避ける表現に寄せる |
ポイント:説明文は「審査を通すための飾り」ではなく、ユーザーが許可判断をするための情報です。アプリの実態に反する説明は避け、長期的には不要なSDK参照を減らす方向で整えるのが理想です。
追加後にやるべき確認:本当にビルド成果物へ反映されているか
Info.plistを編集しても、ビルド構成やプロジェクト構成によっては別のInfo.plistが使われたり、キャッシュが効いて反映されていないように見えることがあります。次のチェックを行うと安全です。
チェックリスト
| チェック項目 | 確認方法 | よくある落とし穴 |
|---|---|---|
| ビルド対象がReleaseになっている | アップロードした構成(Release/TestFlight用)でビルドする | Debugで直してもReleaseのInfo.plistが別管理 |
| 最終的なInfo.plistにキーが入っている | .app内のInfo.plistを確認する(後述コマンド例) | 編集したファイルが採用されていない |
| ビルド番号を更新して再アップロードした | App Store Connectは同一ビルド番号の再アップロード不可 | 同じビルド番号で「直ったはず」と勘違い |
| クリーンビルドを実施した | bin/obj削除、Clean → Rebuild | 古い成果物が残り反映されない |
成果物(.app)内のInfo.plistを確認する例(macOS)
Mac環境がある場合、ビルド後の.appに入っているInfo.plistを直接確認できます。パスは環境で異なるため、目的は「最終成果物にキーが入っているか」を見ることです。
# 例:アプリバンドル内のInfo.plistを表示して、キーを検索する
/usr/libexec/PlistBuddy -c "Print :NSBluetoothAlwaysUsageDescription" "YourApp.app/Info.plist"
値が表示されれば反映されています。エラーになる場合は、キーが入っていないか、パスが違っています。
「どのNuGet/SDKが原因か」を調べる現実的な手順
警告を消すだけならInfo.plist追加で十分なことが多い一方、将来の依存関係整理や不要な権限表示を避けたい場合は、原因を特定しておくとスッキリします。ここでは、.NET MAUI / Xamarin を想定した実務的な追い方を紹介します。
依存関係ツリーを可視化して当たりを付ける
まずは「どのパッケージが何を引っ張っているか」を見える化します。Visual StudioのNuGetパッケージマネージャーで依存関係を辿る方法に加え、CLIだと網羅的に確認できます。
# 直接参照 + 推移的依存関係(transitive)まで一覧表示
dotnet list package --include-transitive
# 出力をファイルに落として検索しやすくする例
dotnet list package --include-transitive > packages.txt
この一覧を見て「デバイス連携」「近接」「Beacon」「BLE」「Peripheral」など、それっぽいキーワードが含まれるものがないか確認します。とはいえ、内部実装で参照している場合は名称から推測できないことも多く、次の手段が確実です。
ビルドログから「CoreBluetooth」が追加される瞬間を探す
.NET MAUI / Xamarin.iOS は内部で mtouch を呼び出してリンクするため、ビルドログに「どのフレームワークをリンクするか」の情報が出ます。ログ出力を詳細にして、CoreBluetoothという文字列を検索するのが手堅いです。
- Visual Studio:ビルド出力の詳細度を「詳細」または「診断」に上げる
- MSBuild:binlogを出して後から検索する(大規模プロジェクトほど有効)
# 例:MSBuildのバイナリログを生成して、後で検索する
dotnet build -c Release -bl:build.binlog
生成したbuild.binlogを「MSBuild Structured Log Viewer」で開き、「CoreBluetooth」や「Bluetooth」で検索します。どのターゲット・どのタスクで参照が追加されているかが追いやすくなります。
アプリバイナリがCoreBluetoothをリンクしているか確認する(macOS)
静的チェックが反応する直接要因は「アプリのバイナリがCoreBluetoothを参照している」ことです。Macが使える場合は、アプリ実行ファイル(Mach-O)に対して otool を実行すると、リンクされているフレームワークを確認できます。
# 例:アプリ実行ファイルがCoreBluetoothを参照しているか確認
otool -L "YourApp.app/YourApp" | grep CoreBluetooth
結果にCoreBluetoothが出てくれば、何らかのコード(自前または依存SDK)がBluetooth関連APIを取り込んでいます。ここまで分かれば、次は「どのSDK由来か」を当てに行きます。
それでも特定できない場合の最終手段:シンボルから逆引きする
より踏み込むなら、Bluetoothに関係するクラス名・シンボル名でバイナリを検索します。たとえばCoreBluetooth関連の代表的なクラス名にはCBCentralManager、CBPeripheralなどがあります。
# 例:バイナリ内の文字列からBluetooth関連の痕跡を探す
strings "YourApp.app/YourApp" | grep -E "CBCentralManager|CBPeripheral|CoreBluetooth"
一致が多く出る場合は、どこかのSDKが内部でBluetooth機能を抱えている可能性が高いです。ここまでの情報と、依存関係ツリーを突き合わせて、候補パッケージを絞り込みます。
リンカー設定の見直し:Don’t Linkのままで良いか?
「Don’t Link」はデバッグでは役に立つ一方、配布用ビルドではリスクもあります。アプリサイズ増加、起動時間、そして今回のような不要なフレームワーク参照の残存に繋がりやすいからです。
| 設定イメージ | 特徴 | 向いている場面 | 注意点 |
|---|---|---|---|
| Don’t Link(None) | 未使用コードも残りやすい | 原因調査、デバッグ | 不要参照が残って警告やサイズ増の原因になりやすい |
| Link SDK Only(SdkOnly) | SDK由来の未使用部分を削る | 多くのアプリのRelease向け | 一部の反射利用ライブラリで追加設定が必要になることがある |
| Link All(Full) | 最大限削る | サイズ最優先のとき | リンク切れのリスクが高く、要検証 |
.NET MAUI / Xamarin.iOSでは、プロジェクト設定やcsprojでリンク設定を固定することもできます(チーム開発やCIでの揺れ防止に有効)。
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<MtouchLink>SdkOnly</MtouchLink>
</PropertyGroup>
ただし、リンクを強めるほど「実行時に必要な型が消える」事故も起きやすくなります。SDKやリフレクションを使うライブラリを導入している場合は、リリース前に必ず実機で機能テストを行ってください。
ついでに整理しておきたい:iOSでよく要求されるプライバシーキー
ITMS-90683はBluetoothだけでなく、他のプライバシー機能でも同系統の警告が出ることがあります。今後のSDK追加で慌てないために、代表的なものを一度棚卸ししておくと運用が楽になります。
| 機能 | Info.plistのキー例 | 用途の例 |
|---|---|---|
| Bluetooth | NSBluetoothAlwaysUsageDescription | 周辺機器接続、近接機器検出 |
| 位置情報 | NSLocationWhenInUseUsageDescription など | 地図、現在地検索、周辺店舗検索 |
| カメラ | NSCameraUsageDescription | 撮影、QRコード読み取り |
| マイク | NSMicrophoneUsageDescription | 音声入力、通話、録音 |
| 写真ライブラリ | NSPhotoLibraryUsageDescription など | 画像選択、保存 |
外部SDKを追加したら「何の権限が増えたか」を毎回チェックする運用にしておくと、TestFlightアップロード直前に慌てずに済みます。
よくある質問
NSBluetoothAlwaysUsageDescriptionを追加すると、必ず許可ダイアログが出ますか?
いいえ。基本的に許可ダイアログは、アプリ(または内部SDK)がBluetooth関連APIへアクセスし、iOSが許可確認を必要と判断したタイミングで表示されます。キーを入れたからといって、何もしないのに即表示されるわけではありません。ただし、導入しているSDKが起動時にBluetoothを初期化している場合は、想定外に表示される可能性があります。
本当にBluetoothを使っていないなら、説明文を入れるだけで良いのでしょうか?
短期的な対処としては有効です。一方で、ユーザーに誤解を与えたくない、将来の審査リスクを減らしたい、という場合は「なぜ参照されているのか」を特定し、不要なSDKや機能を外すのが理想です。どうしても外せない場合でも、文言は誇張せず、実態に沿った説明に寄せてください。
「Bluetoothという単語でコード検索しても出ない」のに検知されるのはなぜ?
自前コードではなく、ビルド後にリンクされたネイティブ側(iOSのフレームワーク参照)を検知しているためです。SDK内部やバインディングされたネイティブコードに参照が含まれると、リポジトリ上の検索だけでは見つかりません。依存関係ツリー、ビルドログ、otoolによるリンク確認の順で追うと、原因にたどり着きやすくなります。
まとめ:最短で直し、必要なら原因も追って再発を防ぐ
- ITMS-90683は「Bluetoothを使う可能性があるのに、Info.plistに目的説明が無い」系の警告
- 自前コードで未使用でも、NuGet/外部SDKがCoreBluetoothを参照しているだけで検知される
- 最短の解決は NSBluetoothAlwaysUsageDescription をInfo.plistへ追加して再アップロード
- 原因を特定したい場合は、依存関係ツリー → ビルドログ検索 → otoolでリンク確認の順が現実的
- Don’t Linkは参照が残りやすいので、ReleaseではLink設定の見直しも検討する
まずは警告を確実に消して提出を前に進め、落ち着いたタイミングで依存関係とリンク設定を整えると、今後のアップロードや審査が安定します。

コメント