ITMS-91053 修正:Expo 缺少 API 宣告
作者:Mehdi Azizi
發布於 ,8 分鐘
ITMS-91053 是 build 用了需宣告理由的 API 卻沒宣告。在 ios.privacyManifests 加上類別與代碼,再重新 build。
ITMS-91053 是 Apple 的自動檢查,會在你上傳 build 之後用 e-mail 寄給你。它的意思是:你 app 的程式碼用了「需宣告理由」的 API(偏好設定、檔案時間戳、開機後經過時間、磁碟空間或鍵盤),卻沒有在隱私權清單裡說明原因。用 Expo 的話,修正方式是在 ios.privacyManifests 裡加上 Apple 指出的類別、描述你實際用途的代碼,然後重新 build。
在 Solys(我做的小遊戲鬧鐘)上,這些理由都宣告在 app 的設定裡。下面是它真實的宣告內容、列出相依套件用了哪些 API 的指令,以及上傳前要對最終檔案做的檢查。
ITMS-91053 錯誤是什麼意思?
這封信的標題是「ITMS-91053: Missing API declaration」。它指出你 build 裡某個特定檔案的程式碼,引用了一個或多個需要宣告理由的 API。接著會列出相關的類別,例如 NSPrivacyAccessedAPICategoryUserDefaults。
這些 API 不需要向使用者要求任何權限。但它們可以被用來辨識裝置,也就是 Apple 說的 fingerprinting。這是被禁止的,就算使用者同意追蹤也一樣。Apple 在需宣告理由的 API 頁面說明:每個 app 和每個 SDK 都必須宣告使用它們的原因。
同一頁也定下規則:**自 2024 年 5 月 1 日起,App Store Connect 不再接受沒在清單中說明這些用途的 app。**有開發者回報,這封信只是個警告,build 還是被接受了。別指望這樣:把它當成阻擋,在下次上傳前修好。
跟其他 ITMS 錯誤一樣,這不是審查人員退件。沒有人打開你的 app:是 Apple 分析了你的二進位檔。
iOS 隱私權清單:PrivacyInfo.xcprivacy 檔案
隱私權清單是一個名為 PrivacyInfo.xcprivacy 的檔案,放在 app bundle 的根目錄。根據 Apple 關於 privacy manifests 的文件,它描述收集的資料、追蹤行為,以及需宣告理由的 API。後者放在 NSPrivacyAccessedAPITypes 陣列裡:每個類別一筆,包含類型和理由清單。
容易踩雷的地方:**一份清單只涵蓋它自己的 bundle。**Apple 寫得很清楚:每個使用這些 API 的執行檔或動態函式庫,都必須在自己的 bundle 裡有一份清單。你 app 的清單不涵蓋內嵌的 framework,SDK 也不能靠你 app 的清單。
別把它跟 ITMS-90683 要求的 Info.plist 說明文字搞混。那些文字會在權限請求中顯示給使用者看。隱私權清單則只有 Apple 會讀。
5 個 API 類別與理由代碼
類別的完整名稱以 NSPrivacyAccessedAPICategory 開頭。API 和理由的清單在 NSPrivacyAccessedAPIType 這個 key 的頁面上,我在 2026 年 10 月 15 日查閱。
| 類別 | 典型 API | 常見理由 |
|---|---|---|
| UserDefaults | UserDefaults | CA92.1 |
| FileTimestamp | creationDate | C617.1 |
| SystemBootTime | systemUptime | 35F9.1 |
| DiskSpace | volumeAvailableCapacityKey | E174.1 |
| ActiveKeyboards | activeInputModes | 54BD.1 |
你最常遇到的代碼:
- CA92.1:app 讀寫自己的偏好設定,其他 app 無法存取。
- 1C8F.1:跟同一個 App Group 的 widget 或 extension 共用偏好設定。
- C617.1:存放在 app 容器內檔案的日期或大小。
- 3B52.1:使用者選取的檔案,例如透過文件選擇器。
- 35F9.1:測量 app 內兩個事件之間經過的時間,或執行計時器。
- E174.1:寫入檔案前檢查剩餘空間,且對使用者有可見的影響。
- 85F4.1:向使用者顯示磁碟空間。
**宣告符合你實際用途的理由,而不是看起來涵蓋最廣的那個。**Apple 明確表示,這些 API 和它們的資料只能用於宣告的理由,絕不能用於追蹤。有兩個代碼是保留給第三方 SDK 的:C56D.1 和 0A2A.1。它們不該出現在你 app 的清單裡。
在 Expo 中宣告隱私權清單
用 Expo 的話,你不用自己寫這個檔案。你在 app.json 或 app.config 的 expo.ios.privacyManifests 底下描述它,prebuild 就會產生 PrivacyInfo.xcprivacy。這是 Expo 關於 privacy manifests 的文件提供的方法。
以下是 Solys 設定的真實片段。這個 key 裡還有追蹤和收集資料的內容,這裡先拿掉了:
{
"expo": {
"ios": {
"privacyManifests": {
"NSPrivacyAccessedAPITypes": [
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryUserDefaults",
"NSPrivacyAccessedAPITypeReasons": ["CA92.1", "1C8F.1"]
},
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategoryFileTimestamp",
"NSPrivacyAccessedAPITypeReasons": ["C617.1"]
},
{
"NSPrivacyAccessedAPIType": "NSPrivacyAccessedAPICategorySystemBootTime",
"NSPrivacyAccessedAPITypeReasons": ["35F9.1"]
}
]
}
}
}
}
CA92.1、C617.1 和 35F9.1 對應的是 React Native 和 app 裡 Expo 模組所宣告的內容。1C8F.1 則來自 Solys 本身:app 透過 App Group 跟它的 widget 共用設定。
有個陷阱要知道:如果你的 ios 資料夾是自動產生並被 git 忽略的,**你在這個資料夾裡手寫的 PrivacyInfo.xcprivacy 永遠不會進到 build 裡。**EAS 會重跑 prebuild,重新產生整個資料夾。我在 2026 年 9 月 9 日的一個專案上親眼看到:手寫的清單在 prebuild 之後就不見了。宣告必須放在設定裡。
最後一點:清單是二進位檔的一部分。遠端更新改不了它。你必須重新 build 再上傳。
自帶清單的第三方 SDK
大部分較新的函式庫都會附上自己的清單。使用這些 API 的 Expo 套件在它們的 ios 資料夾裡都有 PrivacyInfo.xcprivacy,React Native 也一樣。問題出在它們連結到你 app 的方式。
Expo 的文件提醒:Apple 無法正確讀取所有以靜態方式連結的 CocoaPods 相依套件的清單,而 Expo 套件正是這種情況。所以你可能得把它們的理由複製到你 app 的設定裡。
為了減少這種工作,expo-build-properties 的 ios.privacyManifestAggregationEnabled 設定會把 CocoaPods 相依套件的清單合併成單一檔案。在 Expo 為我的專案(SDK 57)產生的 Podfile 裡,這個合併預設是開啟的,除非你把它關掉。
合併有個限制:它只看得到有提供清單的相依套件。沒有清單的舊函式庫就看不到。所以要讀 e-mail 裡指出的檔案路徑:它告訴你該去哪裡修。
| 指出的檔案 | 修正位置 |
|---|---|
| app 的執行檔 | ios.privacyManifests |
某個 .framework | 更新該 SDK |
某個 .appex extension | extension 的清單 |
上傳前先檢查
Expo 建議在上傳後等 Apple 的 e-mail。這樣可行,但每來回一次就要多花一次 build。以下是我在上傳前做的四項檢查。
**1. 列出相依套件宣告了什麼。**在專案根目錄執行:
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*' \
-exec grep -ho "NSPrivacyAccessedAPICategory[A-Za-z]*" {} + | sort | uniq -c
在 Solys 上,2026 年 10 月 15 日執行這個指令,找到 13 份清單和四個類別:檔案時間戳、偏好設定、開機後經過時間和磁碟空間。列出的每個類別都必須出現在 app 的最終清單裡,或出現在使用它的 framework 的清單裡。
**2. 搜尋你自己的原生程式碼。**自製模組、plugin、像 widget 這樣的 target:這些程式碼沒有任何 SDK 會替你宣告。
grep -rlE "UserDefaults|systemUptime|mach_absolute_time|creationDate|modificationDate|volumeAvailableCapacity|activeInputModes" \
--include='*.swift' --include='*.m' --include='*.mm' modules plugins targets
依你的專案調整資料夾。在 Solys 上,就是這個搜尋找出了透過 App Group 共用的偏好設定,所以才有 1C8F.1 這個代碼。
**3. 讀產生出來的清單。**執行 npx expo prebuild -p ios,然後打開 ios/<YourAppName>/PrivacyInfo.xcprivacy。注意:這個指令可能會刪掉再重建 ios 資料夾。如果你在裡面手動改過東西,請在專案的副本上操作。
**4. 檢查最終 build。**這是唯一算數的證據。從 expo.dev 的 build 頁面下載 IPA,然後在你的 Mac 上執行:
unzip -q MyApp.ipa -d ipa
find ipa/Payload -name PrivacyInfo.xcprivacy
plutil -p ipa/Payload/*.app/PrivacyInfo.xcprivacy
第二行會列出所有內嵌的清單,包括 app 的和 framework 的。第三行會顯示 app 的清單,拿來跟第 1 步的清單比對。
清單要跟你的商店頁面說法一致
清單不是單獨存在的。如果它宣告了追蹤或收集資料,你的 App Store 頁面和隱私權政策也必須說一樣的事。三者之間有落差,之後可能會依 guideline 5.1.1 被人工審查退件。要對齊哪些內容,詳見app 的隱私權政策。
上傳 build 不再有意外
只要看得懂,ITMS 錯誤很快就能修好。接下來的退件則需要多一點判斷:處理方式在被 Apple 退件的 app 這頁,最常見的原因在Apple 為什麼退你的 app。
想在上傳前檢查你的資料,Apple Review Kit 收錄了我的檢查清單,以及會幫你審視專案的 Claude Code skills。如果上傳卡住了,想找人幫你看看,跟我預約一小時:我們透過視訊,在你的螢幕上一起檢查專案。如果你正在準備第一次上架,做免費診斷:它會幫你把步驟排好順序,並寄給你我的 Apple 防退件檢查清單。
常見問題
ITMS-91053 會擋住我的 build 上傳嗎?
Apple 寫明,自 2024 年 5 月 1 日起,App Store Connect 不再接受沒說明這些 API 用途的 app。不過也有開發者在 build 被接受的情況下收到這封信,當作警告。不管哪種情況,都要在下次 build 前修好清單。
UserDefaults 該選哪個理由代碼?
如果 app 只讀寫自己的偏好設定,選 CA92.1。如果它透過 App Group 跟 widget 或 extension 共用,再加上 1C8F.1。C56D.1 是保留給第三方 SDK 的:不要放進你 app 的清單。
每個 SDK 的清單都要複製到 app.json 嗎?
不一定。如果清單合併有開啟,CocoaPods 相依套件的清單會在安裝 pods 時合併在一起。只有當最終清單缺少某個類別時才需要複製,並附上描述實際用途的理由。
我的 widget 或 extension 需要自己的清單嗎?
根據 Apple,每個包含使用這些 API 的執行檔的 bundle,都必須有自己的清單。extension 是獨立的執行檔,有自己的 bundle:app 的清單涵蓋不到它。
不重新 build 能修正 ITMS-91053 嗎?
不能。隱私權清單是二進位檔的一部分。修正設定、重新 build,然後先在 TestFlight 測試,再送審。