App Store Connect にビルドをアップロードした数分後、件名に「ITMS-91053」と書かれたメールが届く——個人開発で長くアプリを出してきましたが、この警告に最初に当たったときは正直、何を指摘されているのか分かりませんでした。自分ではプライバシーに関わる API を書いた覚えがないからです。
Rork が生成するのは Expo(React Native)のアプリで、ネイティブのコードは自分でほとんど書きません。にもかかわらずこの警告が出るのは、原因が「自分の JavaScript」ではなく、その下で動く Expo や各種 SDK のネイティブ層にあるからです。ここを理解しないまま提出を繰り返すと、審査に出す前のリジェクトで時間を溶かし続けることになります。Rork で出したアプリを想定し、Privacy Manifest 周りを提出前に潰すための実務を、私自身が踏んだ順番で具体的に追っていきます。
ITMS-91053 と ITMS-91061 はまったく別の問題です
まず混同しがちな2つを分けます。両方ともメールで届きますが、原因も対処も違います。
ITMS-91053 は「Missing API declaration」、つまり Required Reason API(後述)を使っているのに、その理由を PrivacyInfo.xcprivacy に宣言していないという指摘です。これは多くの場合、自分のアプリ本体(または薄いネイティブ層)の話です。
ITMS-91061 は「Missing privacy manifest」で、Apple が指定する『よく使われるサードパーティ SDK』が、署名付きのマニフェストを同梱していないという指摘です。AdMob(Google Mobile Ads SDK)や各種解析 SDK がここに該当します。こちらは自分では直せず、SDK のバージョンを上げて解決するのが基本です。
最初の頃の私は、この2つを同じ「プライバシーの警告」として一緒に直そうとして、片方だけ直して再提出し、また同じメールが来る、というループにはまりました。最初に番号で切り分けるのが結局いちばん早いです。
Required Reason API とは何で、なぜ Expo アプリで踏むのか
Required Reason API は、フィンガープリンティングに悪用されやすい一部の OS API です。使うこと自体は許されていますが、「なぜ使うのか」を理由コードで宣言する義務があります。代表的なのは次の4カテゴリです。
ファイルのタイムスタンプ(NSPrivacyAccessedAPICategoryFileTimestamp)、ディスク空き容量(DiskSpace)、システム起動時刻(SystemBootTime)、そして UserDefaults(UserDefaults)です。
問題は、Expo アプリではこれらを「自分で呼んでいなくても踏む」点にあります。UserDefaults は React Native の AsyncStorage や多くのライブラリが内部で使いますし、ファイルのタイムスタンプはキャッシュ管理ライブラリが触ります。つまり Rork で機能を足していくほど、知らないうちに該当 API が増えていきます。
PrivacyInfo.xcprivacy の最小実装
PrivacyInfo.xcprivacy は plist 形式のファイルです。Rork 生成アプリで現実的に踏みやすい4つを宣言した最小例が次のものです。理由コードは Apple が定義した固定文字列で、勝手な値は使えません。
<? xml version = "1.0" encoding = "UTF-8" ?>
<! DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
< plist version = "1.0" >
< dict >
< key >NSPrivacyTracking</ key >
< false />
< key >NSPrivacyCollectedDataTypes</ key >
< array />
< key >NSPrivacyAccessedAPITypes</ key >
< array >
< dict >
< key >NSPrivacyAccessedAPIType</ key >
< string >NSPrivacyAccessedAPICategoryUserDefaults</ string >
< key >NSPrivacyAccessedAPITypeReasons</ key >
< array >< string >CA92.1</ string ></ array >
</ dict >
< dict >
< key >NSPrivacyAccessedAPIType</ key >
< string >NSPrivacyAccessedAPICategoryFileTimestamp</ string >
< key >NSPrivacyAccessedAPITypeReasons</ key >
< array >< string >C617.1</ string ></ array >
</ dict >
< dict >
< key >NSPrivacyAccessedAPIType</ key >
< string >NSPrivacyAccessedAPICategoryDiskSpace</ string >
< key >NSPrivacyAccessedAPITypeReasons</ key >
< array >< string >E174.1</ string ></ array >
</ dict >
< dict >
< key >NSPrivacyAccessedAPIType</ key >
< string >NSPrivacyAccessedAPICategorySystemBootTime</ string >
< key >NSPrivacyAccessedAPITypeReasons</ key >
< array >< string >35F9.1</ string ></ array >
</ dict >
</ array >
</ dict >
</ plist >
CA92.1 は「アプリ自身のデータをアプリ内に保存するため」という UserDefaults の理由コードで、AsyncStorage を使うアプリはほぼこれに該当します。理由コードを間違えると、宣言してあっても弾かれるので、Apple のドキュメントで自分の用途に一致するものを選ぶのが大事です。
本当の難所は SDK 側のマニフェスト漏れ(ITMS-91061)
自分のアプリ本体を直しても ITMS-91061 が残る場合、原因は同梱した SDK です。私の場合、収益化のために AdMob を入れた瞬間にこの警告が出ました。
対処は次の手順です。
メール本文に列挙された SDK 名を確認する(例: GoogleMobileAds)。
その SDK が privacy manifest 対応済みのバージョンを調べる。
package.json で対応版へ上げ、npx expo prebuild --clean で iOS プロジェクトを作り直す。
再ビルドして提出し、警告が消えたかを確認する。
ここで prebuild --clean を省くと、古いネイティブ成果物が残って「上げたはずなのに直らない」状態になります。私はこれで一度ハマったので、SDK 更新後は必ずクリーンビルドにしています。
トラッキングしていないのに NSPrivacyTracking で迷う場面
AdMob を入れていても、パーソナライズ広告を使わず、ATT(App Tracking Transparency)でトラッキングしない設計なら NSPrivacyTracking は false のままで構いません。ただしこの判断は広告の設定と一致している必要があります。
非パーソナライズ広告のみに固定しているなら、トラッキング扱いにはなりません。逆に、後からパーソナライズ広告を有効化したのにマニフェストを false のままにしておくと、申告と実態の不一致になります。収益化の設定を変えたら、マニフェストも見直すのが安全です。
提出前の最終チェック
次にビルドを提出する前に、3点だけ確認してください。PrivacyInfo.xcprivacy が iOS ターゲットに含まれているか、宣言した理由コードが用途と一致しているか、そして同梱 SDK がすべてマニフェスト対応版か。この3点が揃っていれば、ITMS-91053 と ITMS-91061 の大半は提出前に消えます。
審査落ちは精神的に削られますが、Privacy Manifest 関連はパターンが決まっているぶん、一度仕組みを作れば次から悩まなくなります。同じところで足止めされている方の参考になれば幸いです。