ITMS-91053: แก้ Missing API declaration ด้วย Expo
โดย Mehdi Azizi
เผยแพร่เมื่อ , 8 นาที
ITMS-91053: บิลด์ของคุณใช้ API ที่ต้องระบุเหตุผลแต่ไม่ได้ประกาศ ให้เพิ่มหมวดหมู่และโค้ดเหตุผลใน ios.privacyManifests แล้วบิลด์ใหม่
ITMS-91053 คือการตรวจสอบอัตโนมัติของ Apple ซึ่งจะส่งมาทางอีเมลหลังการส่งบิลด์ของคุณ ข้อความนี้บอกว่าโค้ดในแอปของคุณใช้ API แบบ "ต้องระบุเหตุผล" (การตั้งค่า, วันที่ของไฟล์, เวลาตั้งแต่เปิดเครื่อง, พื้นที่ดิสก์ หรือคีย์บอร์ด) โดยไม่ได้อธิบายเหตุผลไว้ในไฟล์ manifest ด้านความเป็นส่วนตัว ถ้าใช้ Expo ให้แก้ใน ios.privacyManifests: ใส่หมวดหมู่ที่ Apple ระบุมา ใส่โค้ดที่ตรงกับการใช้งานจริงของคุณ แล้วบิลด์ใหม่
ใน Solys แอปนาฬิกาปลุกพร้อมมินิเกมของผม เหตุผลเหล่านี้ประกาศไว้ในคอนฟิกของแอป ในบทความนี้ผมจะให้ดูการประกาศจริงของแอป คำสั่งที่ใช้ดูว่า dependency ของคุณใช้อะไรบ้าง และการตรวจไฟล์สุดท้ายก่อนส่ง
ข้อผิดพลาด ITMS-91053 หมายความว่าอะไร?
ข้อความนี้มีหัวเรื่องว่า "ITMS-91053: Missing API declaration" โดยบอกว่าโค้ดในไฟล์หนึ่งของบิลด์คุณอ้างถึง API ที่ต้องระบุเหตุผลอย่างน้อยหนึ่งตัว จากนั้นจะแสดงรายชื่อหมวดหมู่ที่เกี่ยวข้อง เช่น NSPrivacyAccessedAPICategoryUserDefaults
API เหล่านี้ไม่ต้องขออนุญาตจากผู้ใช้ แต่สามารถนำไปใช้ระบุตัวอุปกรณ์ได้ ซึ่ง Apple เรียกว่า fingerprinting และเป็นสิ่งต้องห้าม แม้ผู้ใช้จะยอมรับการติดตามแล้วก็ตาม Apple อธิบายไว้ในหน้าเรื่อง API ที่ต้องระบุเหตุผล ว่าทุกแอปและทุก SDK ต้องประกาศว่าใช้ API เหล่านี้เพื่ออะไร
หน้าเดียวกันกำหนดกฎไว้ว่า ตั้งแต่วันที่ 1 พฤษภาคม 2024 App Store Connect จะไม่รับแอปที่ไม่ได้อธิบายการใช้งานเหล่านี้ไว้ใน manifest มีนักพัฒนาบางคนบอกว่าได้รับอีเมลนี้เป็นแค่คำเตือนบนบิลด์ที่ผ่านแล้ว แต่อย่าหวังพึ่งตรงนั้น ให้ถือว่าเป็นตัวบล็อกและแก้ให้เสร็จก่อนส่งครั้งต่อไป
เหมือนข้อผิดพลาด ITMS อื่นๆ นี่ไม่ใช่การปฏิเสธจาก reviewer ไม่มีใครเปิดแอปของคุณ Apple แค่วิเคราะห์ไบนารี
Privacy manifest ของ iOS: ไฟล์ PrivacyInfo.xcprivacy
Privacy manifest คือไฟล์ชื่อ PrivacyInfo.xcprivacy ที่วางไว้ที่รากของ bundle ของแอป ตามเอกสารของ Apple เรื่อง privacy manifests ไฟล์นี้อธิบายข้อมูลที่เก็บ การติดตาม และ API ที่ต้องระบุเหตุผล ส่วนหลังสุดนี้อยู่ในอาร์เรย์ NSPrivacyAccessedAPITypes: หนึ่งรายการต่อหนึ่งหมวดหมู่ พร้อมประเภทและรายการเหตุผล
จุดที่มักพลาด: manifest ครอบคลุมเฉพาะ bundle ของตัวเองเท่านั้น Apple เขียนไว้ชัดว่าทุก executable หรือ dynamic library ที่ใช้ API เหล่านี้ต้องมี manifest ใน bundle ของตัวเอง manifest ของแอปไม่ครอบคลุม framework ที่ฝังมา และ SDK ก็พึ่ง manifest ของแอปคุณไม่ได้
อย่าสับสนกับข้อความใน Info.plist ที่ ITMS-90683 กำหนด ข้อความพวกนั้นแสดงให้ผู้ใช้เห็นตอนขออนุญาต ส่วน manifest นั้นมีแค่ Apple ที่อ่าน
API 5 หมวดหมู่และโค้ดเหตุผล
ชื่อเต็มของหมวดหมู่ขึ้นต้นด้วย NSPrivacyAccessedAPICategory รายชื่อ API และเหตุผลอยู่ในหน้าของคีย์ NSPrivacyAccessedAPIType ซึ่งผมอ่านเมื่อวันที่ 15 ตุลาคม 2026
| หมวดหมู่ | API ที่พบบ่อย | เหตุผลที่ใช้บ่อย |
|---|---|---|
| UserDefaults | UserDefaults | CA92.1 |
| FileTimestamp | creationDate | C617.1 |
| SystemBootTime | systemUptime | 35F9.1 |
| DiskSpace | volumeAvailableCapacityKey | E174.1 |
| ActiveKeyboards | activeInputModes | 54BD.1 |
โค้ดที่คุณจะเจอบ่อยที่สุด:
- CA92.1: แอปอ่านและเขียนการตั้งค่าของตัวเอง ซึ่งแอปอื่นเข้าถึงไม่ได้
- 1C8F.1: การตั้งค่าที่แชร์กับวิดเจ็ตหรือ extension ใน App Group เดียวกัน
- C617.1: วันที่หรือขนาดของไฟล์ที่เก็บอยู่ใน container ของแอป
- 3B52.1: ไฟล์ที่ผู้ใช้เลือกเอง เช่น จากตัวเลือกเอกสาร
- 35F9.1: วัดเวลาที่ผ่านไประหว่างสองเหตุการณ์ในแอป หรือใช้จับเวลา
- E174.1: ตรวจพื้นที่ว่างก่อนเขียนไฟล์ โดยมีผลที่ผู้ใช้มองเห็นได้
- 85F4.1: แสดงพื้นที่ดิสก์ให้ผู้ใช้ดู
ประกาศเหตุผลที่ตรงกับการใช้งานจริงของคุณ ไม่ใช่เหตุผลที่ดูกว้างที่สุด Apple ระบุว่า API เหล่านี้และข้อมูลของมันใช้ได้เฉพาะตามเหตุผลที่ประกาศ และห้ามใช้เพื่อการติดตามเด็ดขาด มีสองโค้ดที่สงวนไว้สำหรับ SDK ภายนอก คือ C56D.1 และ 0A2A.1 ซึ่งไม่ควรอยู่ใน manifest ของแอปคุณ
ประกาศ privacy manifest ใน Expo
ถ้าใช้ Expo คุณไม่ต้องเขียนไฟล์เอง ให้อธิบายไว้ใต้ expo.ios.privacyManifests ใน app.json หรือ app.config แล้ว prebuild จะสร้าง PrivacyInfo.xcprivacy ให้ นี่คือวิธีตามเอกสารของ Expo เรื่อง privacy manifests
นี่คือส่วนหนึ่งจากคอนฟิกจริงของ Solys คีย์นี้มีเรื่องการติดตามและข้อมูลที่เก็บด้วย แต่ผมตัดออกไว้ตรงนี้:
{
"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 และโมดูล Expo ของแอปประกาศไว้ ส่วน 1C8F.1 มาจาก Solys เอง เพราะแอปแชร์การตั้งค่ากับวิดเจ็ตผ่าน App Group
กับดักที่ควรรู้: ถ้าโฟลเดอร์ ios ของคุณถูกสร้างอัตโนมัติและถูก git ignore ไว้ ไฟล์ PrivacyInfo.xcprivacy ที่เขียนเองในโฟลเดอร์นั้นจะไม่มีวันเข้าไปอยู่ในบิลด์ เพราะ EAS จะรัน prebuild ใหม่และสร้างโฟลเดอร์ขึ้นมาใหม่ ผมเจอเองในโปรเจกต์หนึ่งเมื่อวันที่ 9 กันยายน 2026: manifest ที่เขียนเองหายไปหลัง prebuild การประกาศต้องอยู่ในคอนฟิก
อีกเรื่องสุดท้าย: manifest เป็นส่วนหนึ่งของไบนารี การอัปเดตแบบ remote ไม่ได้เปลี่ยนมัน ต้องบิลด์ใหม่แล้วส่งอีกครั้ง
SDK ภายนอกที่มี manifest ของตัวเอง
ไลบรารีใหม่ๆ ส่วนใหญ่มี manifest มาให้ แพ็กเกจ Expo ที่ใช้ API เหล่านี้มี PrivacyInfo.xcprivacy อยู่ในโฟลเดอร์ ios ของตัวเอง React Native ก็เช่นกัน ปัญหาอยู่ที่วิธีที่มันถูกลิงก์เข้ากับแอปของคุณ
เอกสารของ Expo เตือนไว้ว่า Apple อ่าน manifest ของ dependency CocoaPods ที่ลิงก์แบบ static ได้ไม่ถูกต้องทั้งหมด ซึ่งแพ็กเกจ Expo เป็นแบบนั้น ดังนั้นคุณอาจต้องคัดลอกเหตุผลของมันมาใส่ในคอนฟิกของแอปเอง
เพื่อลดงานส่วนนี้ การตั้งค่า ios.privacyManifestAggregationEnabled ของ expo-build-properties จะรวม manifest ของ dependency CocoaPods เข้าเป็นไฟล์เดียว ใน Podfile ที่ Expo สร้างในโปรเจกต์ของผม (SDK 57) การรวมนี้เปิดอยู่ เว้นแต่คุณจะปิดเอง
การรวมนี้มีข้อจำกัด: มันเห็นเฉพาะ dependency ที่มี manifest เท่านั้น ไลบรารีเก่าที่ไม่มี manifest จะมองไม่เห็น ดังนั้นให้อ่านพาธของไฟล์ที่ระบุในอีเมล เพราะมันบอกว่าต้องแก้ตรงไหน
| ไฟล์ที่ถูกระบุ | แก้ที่ไหน |
|---|---|
| executable ของแอป | ios.privacyManifests |
.framework | อัปเดต SDK |
extension .appex | manifest ของ extension |
ตรวจสอบก่อนส่ง
Expo แนะนำให้รออีเมลจาก Apple หลังส่ง ซึ่งก็ใช้ได้ แต่ทุกรอบที่ไปกลับจะเสียบิลด์หนึ่งครั้ง นี่คือการตรวจสี่อย่างที่ผมทำก่อนส่ง
1. ดูรายการที่ dependency ของคุณประกาศไว้ รันจากรากของโปรเจกต์:
find node_modules -name PrivacyInfo.xcprivacy -not -path '*/example/*' \
-exec grep -ho "NSPrivacyAccessedAPICategory[A-Za-z]*" {} + | sort | uniq -c
ใน Solys เมื่อวันที่ 15 ตุลาคม 2026 คำสั่งนี้เจอ manifest 13 ไฟล์และสี่หมวดหมู่ ได้แก่ วันที่ของไฟล์ การตั้งค่า เวลาตั้งแต่เปิดเครื่อง และพื้นที่ดิสก์ ทุกหมวดหมู่ที่เจอต้องไปปรากฏใน manifest สุดท้ายของแอป หรือใน manifest ของ framework ที่ใช้มัน
2. ค้นในโค้ด native ของคุณเอง โมดูลที่เขียนเอง ปลั๊กอิน 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. อ่าน manifest ที่ถูกสร้างขึ้น รัน npx expo prebuild -p ios แล้วเปิด ios/<YourAppName>/PrivacyInfo.xcprivacy ระวัง: คำสั่งนี้อาจลบแล้วสร้างโฟลเดอร์ ios ใหม่ ถ้าคุณแก้อะไรในนั้นด้วยมือ ให้ทำงานบนสำเนาของโปรเจกต์
4. ตรวจบิลด์สุดท้าย นี่คือหลักฐานเดียวที่นับจริง ดาวน์โหลด IPA จากหน้าบิลด์บน expo.dev แล้วรันบน Mac ของคุณ:
unzip -q YourApp.ipa -d ipa
find ipa/Payload -name PrivacyInfo.xcprivacy
plutil -p ipa/Payload/*.app/PrivacyInfo.xcprivacy
บรรทัดที่สองแสดง manifest ทั้งหมดที่ฝังอยู่ ทั้งของแอปและของ framework บรรทัดที่สามแสดง manifest ของแอป ให้เทียบกับรายการจากขั้นตอนที่ 1
สิ่งที่ manifest ต้องสอดคล้องกับหน้าแอปของคุณ
manifest ไม่ได้อยู่ลำพัง ถ้ามันประกาศการติดตามหรือข้อมูลที่เก็บ หน้า App Store และนโยบายความเป็นส่วนตัวของคุณต้องบอกสิ่งเดียวกัน ถ้าสามอย่างนี้ไม่ตรงกัน คุณอาจโดนปฏิเสธจากคนตรวจในภายหลังภายใต้ guideline 5.1.1 รายละเอียดสิ่งที่ต้องทำให้ตรงกันอยู่ในบทความนโยบายความเป็นส่วนตัวของแอป
ส่งบิลด์โดยไม่มีเรื่องเซอร์ไพรส์
ข้อผิดพลาด ITMS แก้ได้เร็วถ้าอ่านเป็น แต่การปฏิเสธที่ตามมาทีหลังต้องคิดมากกว่านั้น วิธีรับมืออยู่ในหน้าแอปถูก Apple ปฏิเสธ และเหตุผลที่พบบ่อยที่สุดอยู่ในทำไม Apple ถึงปฏิเสธแอป
ถ้าต้องการตรวจโปรเจกต์ก่อนส่ง Apple Review Kit รวมเช็กลิสต์ของผมและ skill ของ Claude Code ที่ช่วยรีวิวโปรเจกต์ของคุณไว้ ถ้าการส่งติดขัดและอยากให้คนช่วยดู จองเวลาหนึ่งชั่วโมงกับผม แล้วเราจะไล่ดูโปรเจกต์ของคุณผ่านวิดีโอคอลบนหน้าจอของคุณ และถ้าคุณกำลังเตรียมปล่อยแอปแรก ทำแบบประเมินฟรี ซึ่งจะเรียงขั้นตอนให้ถูกลำดับและส่งเช็กลิสต์กันโดน Apple ปฏิเสธของผมให้คุณ
คำถามที่พบบ่อย
ITMS-91053 บล็อกการส่งบิลด์ของฉันไหม?
Apple เขียนไว้ว่าตั้งแต่วันที่ 1 พฤษภาคม 2024 App Store Connect จะไม่รับแอปที่ไม่ได้อธิบายการใช้ API เหล่านี้ แต่ก็มีนักพัฒนาที่ได้รับอีเมลนี้บนบิลด์ที่ผ่านแล้วในฐานะคำเตือน ไม่ว่าจะเป็นแบบไหน ให้แก้ manifest ก่อนบิลด์ครั้งต่อไป
ควรเลือกโค้ดเหตุผลอะไรสำหรับ UserDefaults?
ใช้ CA92.1 ถ้าแอปอ่านและเขียนแค่การตั้งค่าของตัวเอง เพิ่ม 1C8F.1 ถ้าแชร์การตั้งค่ากับวิดเจ็ตหรือ extension ผ่าน App Group ส่วน C56D.1 สงวนไว้สำหรับ SDK ภายนอก อย่าใส่ไว้ใน manifest ของแอปคุณ
ต้องคัดลอก manifest ของทุก SDK ลงใน app.json ไหม?
ไม่จำเป็น ถ้าเปิดการรวม manifest ไว้ manifest ของ dependency CocoaPods จะถูกรวมกันตอนติดตั้ง pods ให้คัดลอกหมวดหมู่มาเฉพาะเมื่อมันขาดไปจาก manifest สุดท้าย พร้อมเหตุผลที่ตรงกับการใช้งานจริง
วิดเจ็ตหรือ extension ของฉันต้องมี manifest ของตัวเองไหม?
ตามที่ Apple ระบุ ทุก bundle ที่มี executable ซึ่งใช้ API เหล่านี้ต้องมี manifest ของตัวเอง extension เป็น executable แยกที่อยู่ใน bundle ของตัวเอง manifest ของแอปจึงไม่ครอบคลุมมัน
แก้ ITMS-91053 โดยไม่บิลด์ใหม่ได้ไหม?
ไม่ได้ privacy manifest เป็นส่วนหนึ่งของไบนารี ให้แก้คอนฟิก บิลด์ใหม่ แล้วทดสอบใน TestFlight ก่อนส่งเข้ารีวิว