# แก้ ITMS-90338 Non-public API usage ใน Expo

> ITMS-90338 คือ build ของคุณอ้างถึงเมธอด ฟังก์ชัน หรือคลาสที่ Apple ถือว่าเป็นของภายใน ให้หา dependency ต้นเหตุ อัปเดต แล้ว build ใหม่

หน้าต้นฉบับ: [แก้ ITMS-90338 Non-public API usage ใน Expo](https://devoria.fr/th/blog/itms-90338-non-public-api-usage-expo)
ผู้เขียน: [Mehdi Azizi](https://devoria.fr/th/mehdi-azizi)
เผยแพร่: 2026-11-04
แก้ไขเนื้อหาล่าสุด: 2026-11-04


ITMS-90338 คือการตรวจสอบอัตโนมัติที่ Apple ทำกับ build ของคุณ ข้อความนี้บอกว่าไบนารีของคุณอ้างถึง API ที่ Apple ถือว่าเป็น API ภายใน (private) ได้แก่ เมธอด Objective-C ฟังก์ชัน หรือคลาส **วิธีแก้คือ หาไฟล์ที่อีเมลระบุ ไล่ย้อนไปหา dependency ที่มีชื่อนั้นอยู่ อัปเดตมันหรือเปลี่ยนชื่อเมธอดของคุณ แล้ว build ใหม่**

ถ้าใช้ Expo ชื่อที่ถูกอ้างถึงแทบทุกครั้งมาจากไลบรารี native น้อยครั้งมากที่จะมาจาก JavaScript ของคุณ เมื่อวันที่ 15 ตุลาคม 2026 ผมรันคำสั่งวิเคราะห์ในคู่มือนี้กับ build สำหรับ simulator ของ Solys แอปนาฬิกาปลุกที่มีมินิเกมของผม (Expo SDK 57) และกับ iOS SDK ของ Xcode 26.2 ต่อไปนี้คือวิธีอ่านอีเมล หาตัวการ และแยกแยะ false positive

## ข้อผิดพลาด ITMS-90338 หมายความว่าอะไร?

ข้อความมีหัวเรื่องว่า "ITMS-90338: Non-public API usage" และมีสามแบบ ขึ้นอยู่กับสิ่งที่ Apple เจอในไบนารีของคุณ:

- **"references non-public selectors"**: ชื่อเมธอด Objective-C เช่น `boldFontOfSize:`
- **"references non-public symbols"**: ฟังก์ชัน ซึ่งเขียนโดยมีขีดล่างนำหน้า เช่น `__dyld_get_shared_cache_range`
- **"contains or inherits from non-public classes"**: ชื่อคลาส

แต่ละแบบจะให้ path มา ตามด้วยรายชื่อ ข้อความยังให้คำแนะนำสองข้อ คือ เปลี่ยนชื่อเมธอดของคุณถ้าชื่อตรงกับ API ภายใน และเอา static library ที่มีชื่อเหล่านั้นออก อีเมลฉบับหนึ่งเมื่อกันยายน 2026 ยังแนะนำ `xcrun swift-demangle` เพื่อแปลงชื่อ Swift ที่ถูกเข้ารหัสให้อ่านออก

กฎเบื้องหลังการตรวจนี้คือ [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): แอปใช้ได้เฉพาะ API สาธารณะเท่านั้น [สัญญาอนุญาตของ Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) ก็ย้ำไว้ในข้อ 3.3.1 ว่าห้องเรียก API ภายในใดๆ การรีวิวโดยคนตามกฎนี้มีอธิบายในคู่มือ [guideline 2.5.1](https://devoria.fr/th/blog/guideline-2-5-1-public-api-sdk-expo) ส่วนบทความนี้จะพูดถึงข้อผิดพลาดตอนส่ง build

เหมือนข้อผิดพลาด ITMS อื่นๆ คือไม่มีใครเปิดแอปของคุณเลย Apple วิเคราะห์ไบนารีระหว่างประมวลผล build **Apple ไม่ได้เผยแพร่รายชื่อ API ภายในใดๆ ชื่อในอีเมลของคุณคือรายชื่อเดียวที่เชื่อถือได้**

## อ่าน path: เป็น SDK หรือไฟล์ executable ของคุณ?

path ที่เขียนไว้ก่อนรายชื่อบอกคุณว่าต้องค้นในไฟล์ไหน

- **`Frameworks/Name.framework/Name`**: ชื่อนั้นอยู่ใน dynamic framework ที่ฝังมากับแอป ก็คือใน SDK ตัวใดตัวหนึ่ง ใน build ของ Solys โฟลเดอร์นี้มี 14 framework ได้แก่ Hermes, React Native, โมดูล Expo ที่ส่งมาแบบคอมไพล์แล้ว และไลบรารีรูปภาพ SDWebImage
- **ชื่อแอปของคุณเพียงอย่างเดียว**: ชื่อนั้นอยู่ใน executable หลัก กับ Expo ไฟล์นี้รวมโค้ด native ของคุณและ pod ที่คอมไพล์จากซอร์ส ซึ่งลิงก์แบบ static เป็นค่าเริ่มต้น ไลบรารี native ตัวไหนก็อาจเป็นต้นเหตุได้

## แผนผัง: จากชื่อที่ถูกอ้างถึงไปจนถึงการแก้

ไล่ดูทีละแถวตามลำดับ แถวแรกที่ตรงกับกรณีของคุณคือแนวทาง

| สิ่งที่คุณเห็น              | ค้นที่ไหน              | วิธีแก้               |
| --------------------------- | ---------------------- | --------------------- |
| ชื่อขึ้นต้น `$s` หรือ `_$s` | `xcrun swift-demangle` | เริ่มแผนผังใหม่       |
| หาไม่เจอใน IPA ของคุณ       | `grep` ทั้ง bundle     | ส่ง build ใหม่        |
| ชื่อสาธารณะบน iOS           | header ของ iOS SDK     | ส่ง build ใหม่        |
| `API_UNAVAILABLE(ios)`      | header ของ iOS SDK     | อัปเดตแพ็กเกจ         |
| อยู่ใน `Frameworks/X`       | SDK ที่ให้ X มา        | อัปเดต SDK            |
| selector ในแอปของคุณ        | pod และโมดูล native    | ใส่ prefix หรืออัปเดต |
| symbol `_name` ในแอปของคุณ  | `nm -m` แล้วดู pod     | เปลี่ยนการเรียกใช้    |
| คลาสในแอปของคุณ             | คลาสของคุณและ pod      | ใส่ prefix ให้คลาส    |

แถว "ส่ง build ใหม่" หมายถึง false positive ซึ่งจะอธิบายด้านล่าง

## ขั้นที่ 1: เปิด build ที่ส่งไป

ทำงานกับไฟล์ที่ส่งให้ Apple จริงๆ ไม่ใช่กับโปรเจกต์ของคุณ ดาวน์โหลด IPA จากหน้า build บน expo.dev แล้วรันบน Mac:

```bash
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
```

บรรทัดที่สองแสดงรายการ framework ที่ฝังมา บรรทัดที่สามค้นหาลิงก์ตรงไปยัง framework ภายในของ Apple ซึ่งต้องไม่แสดงอะไรออกมา สำหรับ Solys `otool -L` ไม่เจอ framework ภายในเลย ทั้งใน executable และใน 14 framework

## ขั้นที่ 2: หาไฟล์ที่มีชื่อนั้น

เริ่มแบบกว้างๆ ค้นชื่อที่ถูกอ้างถึง โดยตัดขีดล่างข้างหน้าออก ในทั้ง bundle:

```bash
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
```

คำสั่งนี้แสดงไฟล์ที่มีข้อความนั้น จากนั้นค้นให้ละเอียดขึ้นตามประเภทชื่อ (selector, symbol, คลาส) ในไฟล์ที่เจอ ซึ่งเขียนแทนด้วย `Binary`:

```bash
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
```

**สำหรับ selector ให้ใช้ `strings` ไม่ใช่ `nm`** ใน framework React ของ Solys `nm` แสดงเมธอดพร้อมคลาส เช่น `-[RCTRootView initWithBridge:moduleName:initialProperties:]` แต่ไม่เคยแสดง selector `initWithBridge:` เดี่ยวๆ ส่วน `strings` หาเจอ

สำหรับ symbol ให้อ่านท้ายบรรทัด ใน Solys `nm -m` แสดงเช่น `(undefined) external __dyld_get_image_name (from libSystem)` หมายความว่าไบนารีเรียกฟังก์ชันนี้โดยไม่ได้มีมันอยู่ข้างใน และ libSystem เป็นตัวให้มา ฟังก์ชันตัวนี้เป็นสาธารณะ ตามที่ขั้นถัดไปจะแสดง

## ขั้นที่ 3: ตรวจว่าชื่อนั้นเป็น API ภายในจริงไหม

header ของ iOS SDK อธิบาย API สาธารณะไว้ การค้นไม่กี่วินาทีจะบอกได้ว่าชื่อนั้นอยู่ในนั้นไหม และใช้ได้บนแพลตฟอร์มไหน สำหรับ symbol ให้ตัดขีดล่างที่เพิ่มมาตอนคอมไพล์ออกก่อน: `_SCDynamicStoreCreate` จะกลายเป็น `SCDynamicStoreCreate`

```bash
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
```

เมื่อวันที่ 15 ตุลาคม 2026 กับ iOS SDK 26.2 นี่คือผลของสี่ชื่อที่มาจากอีเมล ITMS-90338 จริง:

| ชื่อที่ถูกอ้างถึง             | ใน iOS SDK           | การตีความ              |
| ----------------------------- | -------------------- | ---------------------- |
| \_dyld_get_shared_cache_range | ไม่มี                | API ภายใน              |
| boldFontOfSize:               | ไม่มี                | API ภายในหรือชื่อชนกัน |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios) | macOS เท่านั้น         |
| isMainFrame                   | property ของ WebKit  | สาธารณะ                |

**ชื่อที่มีอยู่และใช้ได้บน iOS ชี้ไปที่ false positive ชื่อที่ไม่มีชี้ไปที่ API ภายในหรือชื่อชนกัน** มีข้อจำกัดสองข้อ: API สาธารณะที่มีเฉพาะใน Swift จะไม่ปรากฏใน header เหล่านี้ และสำหรับ accessor อย่าง `setDelegate:` ให้ค้น property `delegate` แทน

## ขั้นที่ 4: ไล่ย้อนไปหาแพ็กเกจ

ถ้า path ระบุ framework ชื่อของมันจะพาไปหา pod แล้วไปหาแพ็กเกจ npm:

```bash
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
```

`npm explain` บอกว่า dependency ตัวไหนติดตั้งแพ็กเกจนั้นมา ถ้าคุณไม่เคยเพิ่มมันเอง

ถ้า path ระบุ executable ให้ค้นชื่อในซอร์ส native แล้วค้นใน pod:

```bash
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
  "boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
```

ปรับรายชื่อโฟลเดอร์ตามโปรเจกต์ `modules` คือโฟลเดอร์ของโมดูล Expo แบบ local โฟลเดอร์ `ios/Pods` จะปรากฏหลังรัน `npx expo prebuild -p ios` ซึ่งติดตั้ง pod บน Mac ให้ด้วย ระวัง: คำสั่งนี้อาจสร้างโฟลเดอร์ `ios` ใหม่ ถ้าคุณแก้มันด้วยมือไว้ ให้ทำงานกับสำเนา

**การค้นใน `ios/Pods` สำคัญพอๆ กับการค้นในซอร์ส** ใน Solys โฟลเดอร์นี้มี 15 framework ที่ส่งมาแบบคอมไพล์แล้วในรูปแบบ `.xcframework` ได้แก่ โมดูล Expo, Hermes, React Native และ SDK ของบุคคลที่สาม การค้นแค่ไฟล์ซอร์สจะพลาดโค้ดของพวกนี้

## สาเหตุที่พบบ่อย

**SDK ของบุคคลที่สามที่เก่าเกินไปหรือใหม่เกินไป** เมื่อกันยายน 2026 นักพัฒนา Flutter คนหนึ่ง[รายงานว่าโดนปฏิเสธเพราะ SDK ด้านความปลอดภัย](https://github.com/talsec/Free-RASP-Flutter/issues/236): เวอร์ชัน 8.2.3 ของมันเรียก `__dyld_get_shared_cache_range` เวอร์ชัน 8.2.2 ผ่าน และผู้พัฒนา SDK แนะนำให้ย้อนกลับไปใช้ระหว่างรอการแก้ไข

**โมดูล native ที่เรียก API ที่ไม่มีเอกสาร** โมดูลของคุณเอง ปลั๊กอิน หรือไลบรารีเล็กๆ อาจใช้เมธอดภายในของ UIKit ที่เจอในฟอรัม การตรวจจะจับได้จากชื่อ

**ชื่อชนกัน** เมธอดหรือคลาสของคุณบังเอิญมีชื่อเหมือน API ภายในของ Apple ข้อความได้คาดกรณีนี้ไว้แล้วและขอให้คุณเปลี่ยนชื่อ Apple แนะนำให้[ใส่ prefix ให้เมธอดที่เพิ่มเข้าไปในคลาสของ Apple](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html) เช่น ตัวพิมพ์เล็กสามตัวตามด้วยขีดล่าง

**API ที่เป็นสาธารณะบน macOS แต่ไม่ใช่บน iOS** ในปี 2024 แอป .NET MAUI ตัวหนึ่ง[โดนปฏิเสธเพราะ `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949) Apple มีเอกสารของฟังก์ชันนี้จริง แต่[สำหรับ macOS เท่านั้น](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate) และ header ของ iOS SDK ทำเครื่องหมายไว้ว่า `API_UNAVAILABLE(ios, tvos, watchos)`

## แก้โดยไม่หาทางเลี่ยง

1. **อัปเดต dependency** สำหรับแพ็กเกจที่ Expo ดูแล `npx expo install --check` จะแจ้งตัวที่ไม่ตรงกับ SDK ของคุณ ตาม[เอกสารของ Expo CLI](https://docs.expo.dev/more/expo-cli/) สำหรับตัวอื่น ให้อ่าน changelog
2. **ย้อนกลับไปเวอร์ชันก่อนหน้า** ถ้าเวอร์ชันใหม่เป็นตัวที่ทำให้ชื่อนั้นโผล่มา และแจ้งผู้พัฒนาด้วย
3. **เปลี่ยนหรือเอาออก** ไลบรารีที่ถูกทิ้งร้างแล้ว
4. **เปลี่ยนชื่อ** เมธอดหรือคลาสของคุณ โดยใส่ prefix

**อย่าซ่อนชื่อ** การหั่น string หรือเรียกเมธอดผ่านทางอ้อมเป็นแค่การซ่อนการใช้งาน ไม่ใช่การแก้ มันยังผิดกฎ 2.5.1 และสัญญาอยู่ดี และบัญชีของคุณจะเป็นฝ่ายเสี่ยง

จากนั้น build ใหม่พร้อม[หมายเลข build](https://devoria.fr/th/blog/app-store-connect-build-number-version) ใหม่ อัปเดตแบบ remote ไม่พอ เพราะชื่ออยู่ในโค้ด native ที่คอมไพล์แล้ว ก่อนส่ง ให้รันขั้นที่ 2 ซ้ำกับ IPA ใหม่ ชื่อนั้นต้องไม่ปรากฏอีก

## เมื่อเป็น false positive

การตรวจของ Apple ก็ผิดพลาดได้บ้าง เมื่อวันที่ 6 มิถุนายน 2022 แอป React Native และ Flutter ได้รับ ITMS-90338 จากชื่อสาธารณะของ WebKit อย่าง `setNavigationDelegate:` หรือ `isMainFrame` แม้แต่ในอัปเดตที่แทบไม่เปลี่ยนอะไร วันต่อมา วิศวกรของ Apple เขียนว่าปัญหาฝั่งการตรวจสอบได้รับการแก้แล้ว และให้ส่ง build ใหม่ เธรดนี้ยังเก็บไว้ใน[รีโพ GitHub ของ Expo](https://github.com/expo/expo/issues/17768)

ถ้าเจอสามสัญญาณนี้พร้อมกัน ก็น่าจะเป็น false positive:

- build คล้ายกับ build ล่าสุดที่ผ่าน
- ขั้นที่ 2 หาชื่อไม่เจอ หรือขั้นที่ 3 แสดงว่าเป็นชื่อสาธารณะบน iOS
- นักพัฒนาคนอื่นรายงานข้อผิดพลาดเดียวกันในวันเดียวกันบนฟอรัมของ Apple

ในกรณีนี้ ให้ดู[สถานะบริการของ Apple](https://developer.apple.com/system-status/) แล้วส่ง build ใหม่พร้อมหมายเลขใหม่ ถ้ายังเจออยู่ ให้เขียนถึง[ฝ่ายสนับสนุนนักพัฒนาของ Apple](https://developer.apple.com/contact/) พร้อมอีเมลฉบับเต็ม ส่วนชื่อชนกันนั้น Apple ไม่ถือว่าเป็น false positive: ให้เปลี่ยนชื่อ

## ก่อนส่งครั้งถัดไป

ข้อผิดพลาด ITMS อื่นๆ ก็มีคู่มือเช่นกัน: [ITMS-90725](https://devoria.fr/th/blog/itms-90725-sdk-version-issue-expo) เรื่องเวอร์ชัน Xcode, [ITMS-91053](https://devoria.fr/th/blog/itms-91053-missing-api-declaration-expo) เรื่อง privacy manifest และ [ITMS-90034](https://devoria.fr/th/blog/itms-90034-missing-or-invalid-signature-fix) เรื่องลายเซ็น เมื่อ build ผ่านแล้ว ให้ติดตั้งผ่าน [TestFlight](https://devoria.fr/th/blog/testflight-test-iphone-app-before-app-store) ก่อนเข้ารีวิว

การโดน reviewer ปฏิเสธต้องใช้มุมมองที่กว้างกว่านี้: ขั้นตอนอยู่ในหน้า[แอปโดน Apple ปฏิเสธ](https://devoria.fr/th/expertise/app-rejected-by-apple) และเหตุผลที่พบบ่อยที่สุดอยู่ใน[ทำไม Apple ถึงปฏิเสธแอป](https://devoria.fr/th/blog/apple-app-rejection-reasons-real-cases)

ถ้าอยากตรวจความพร้อมก่อนส่ง [Review Kit](https://devoria.fr/th/kits/app-review) รวม checklist ของผมและ skill ของ Claude Code ที่ช่วยตรวจโปรเจกต์ของคุณ ถ้าการส่งติดขัดและคุณอยากให้คนช่วยดู [จองเวลาหนึ่งชั่วโมงกับผม](https://devoria.fr/th/coaching) เราจะดูโปรเจกต์ของคุณด้วยกันผ่านวิดีโอคอลบนหน้าจอของคุณ และถ้าคุณกำลังเตรียมปล่อยแอปแรก [ทำการวินิจฉัยฟรี](https://devoria.fr/th/diagnostic): มันจะเรียงขั้นตอนให้เป็นลำดับ และส่ง checklist กันโดน Apple ปฏิเสธของผมให้คุณ

## คำถามที่พบบ่อย

### ITMS-90338 คือการโดน App Review ปฏิเสธหรือไม่?

ไม่ใช่ครับ มันคือการตรวจอัตโนมัติระหว่างประมวลผล build ก่อนที่คนจะรีวิว build ที่โดนปฏิเสธจะไม่เข้า TestFlight และไม่เข้ารีวิว ส่วน API ภายในที่ reviewer เจอทีหลังจะอยู่ภายใต้ guideline 2.5.1

### แก้ ITMS-90338 ด้วยการอัปเดตแบบ remote ได้ไหม?

ไม่ได้ครับ ชื่อที่ถูกอ้างถึงอยู่ในโค้ด native ที่คอมไพล์อยู่ในไบนารี การอัปเดตแบบ remote เปลี่ยนได้แค่ JavaScript และ resource แก้ dependency แล้ว build ใหม่ จากนั้นส่งอีกครั้ง

### ทำไม Apple ถึงอ้างถึงเมธอดที่ผมไม่เคยเขียน?

เพราะมันมาจากไลบรารีที่ฝังมา หรือมาจากชื่อชนกัน คือ dependency ตัวหนึ่งประกาศเมธอดที่มีชื่อเหมือน API ภายใน Apple อ่านชื่อที่อยู่ในไบนารี ไม่ได้อ่านซอร์สโค้ดของคุณ ไล่ย้อนไปหาแพ็กเกจด้วย `grep` และ `npm explain`

### จะรู้ได้อย่างไรว่า API เป็นสาธารณะบน iOS?

ค้นชื่อของมันใน header ของ iOS SDK ซึ่ง `xcrun --sdk iphoneos --show-sdk-path` จะบอก path ให้ แล้วดูว่าใช้ได้บนแพลตฟอร์มไหน หน้าเอกสารของ Apple ก็ระบุไว้เช่นกัน ฟังก์ชันที่มีเอกสารสำหรับ macOS เท่านั้นไม่ถือว่าเป็นสาธารณะบน iPhone

### ต้องเปลี่ยนหมายเลข build หลังแก้ไหม?

ต้องครับ ให้ EAS เพิ่มเลขให้อัตโนมัติ เพราะหมายเลขที่เคยส่งไปแล้วสำหรับเวอร์ชันเดียวกันอาจโดนปฏิเสธด้วย ITMS-90189


## โปรเจกต์ของคุณ

[Review Kit](https://devoria.fr/th/kits/app-review): เช็กลิสต์รีวิว App Store ของผม การถูก Apple ปฏิเสธที่ผมแก้ไขแล้ว เช็กลิสต์ของ Google Play และ skill ของ Claude Code อีกสามตัว ฿1,849

[iOS & Android Kit](https://devoria.fr/th/kits/ios): สร้างแอป iPhone และ Android ของคุณด้วย Claude Code: โค้ดเริ่มต้นจากแอปของผม เส้นทางจนถึงสโตร์ และชุดคิททั้งหมด ฿3,699

[แบบประเมิน: อะไรกั้นคุณไว้จากแอป iPhone ของคุณ](https://devoria.fr/th/diagnostic): 7 คำถาม 60 วินาที: รู้ว่าอะไรที่ขวางคุณจากการเผยแพร่แอป iPhone จริง ๆ พร้อมเส้นทาง 4 ขั้นตอนและชุดคิทที่เหมาะกับคุณ
