# Lỗi ITMS-90338 Non-public API usage: cách sửa với Expo

> ITMS-90338: build của bạn tham chiếu một phương thức, hàm hoặc lớp mà Apple coi là riêng tư. Hãy tìm dependency gây lỗi, cập nhật nó rồi build lại.

Trang gốc: [Lỗi ITMS-90338 Non-public API usage: cách sửa với Expo](https://devoria.fr/vi/blog/itms-90338-non-public-api-usage-expo)
Tác giả: [Mehdi Azizi](https://devoria.fr/vi/mehdi-azizi)
Ngày đăng: 2026-11-04
Lần sửa nội dung gần nhất: 2026-11-04


ITMS-90338 là một bước kiểm tra tự động của Apple trên build của bạn. Nó báo rằng binary của bạn tham chiếu tới các API mà Apple coi là riêng tư: phương thức Objective-C, hàm hoặc lớp. **Cách sửa: xác định file được nêu trong email, lần ngược tới dependency chứa cái tên đó, cập nhật nó hoặc đổi tên phương thức của bạn, rồi build lại.**

Với Expo, cái tên đó gần như luôn đến từ một thư viện native, hiếm khi từ JavaScript của bạn. Ngày 15 tháng 10 năm 2026, mình đã chạy các lệnh phân tích trong bài này trên một build simulator của Solys, ứng dụng báo thức kèm mini-game của mình (Expo SDK 57), và trên iOS SDK của Xcode 26.2. Dưới đây là cách đọc email, tìm thủ phạm và nhận ra một false positive.

## Lỗi ITMS-90338 nghĩa là gì?

Thông báo có tiêu đề "ITMS-90338: Non-public API usage". Nó có ba biến thể, tùy vào thứ Apple tìm thấy trong binary của bạn:

- **"references non-public selectors"**: tên phương thức Objective-C, như `boldFontOfSize:`;
- **"references non-public symbols"**: các hàm, viết với dấu gạch dưới phía trước, như `__dyld_get_shared_cache_range`;
- **"contains or inherits from non-public classes"**: một tên lớp.

Mỗi biến thể đưa ra một đường dẫn, rồi danh sách các tên. Nội dung email thêm hai lời khuyên: đổi tên phương thức của bạn nếu nó trùng với một API riêng tư, và gỡ bỏ thư viện tĩnh chứa chúng. Một email tháng 9 năm 2026 còn gợi ý dùng `xcrun swift-demangle` để đọc được các tên Swift đã bị mã hóa.

Quy tắc đứng sau bước kiểm tra này là [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): một app chỉ được dùng API công khai. [Thỏa thuận cấp phép của Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) nhắc lại điều này ở điều 3.3.1: không được gọi API riêng tư. Phần duyệt thủ công của quy tắc này được trình bày chi tiết trong bài về [guideline 2.5.1](https://devoria.fr/vi/blog/guideline-2-5-1-api-cong-khai-sdk-expo). Ở đây, ta xử lý lỗi khi gửi build.

Giống các lỗi ITMS khác, chưa ai mở app của bạn cả: Apple đã phân tích binary trong lúc xử lý build. **Apple không công bố danh sách API riêng tư nào: các tên trong email của bạn là danh sách đáng tin duy nhất.**

## Đọc đường dẫn: một SDK hay file thực thi của bạn?

Đường dẫn ghi trước danh sách cho bạn biết cần tìm trong file nào.

- **`Frameworks/Name.framework/Name`**: cái tên nằm trong một framework động được nhúng, tức là trong một SDK cụ thể. Trên build của Solys, thư mục này chứa 14 framework: Hermes, React Native, các module Expo được cung cấp sẵn ở dạng đã biên dịch và thư viện ảnh SDWebImage.
- **Chỉ có tên app của bạn**: cái tên nằm trong file thực thi chính. Với Expo, file này gộp code native của bạn và các pod được biên dịch từ mã nguồn, mặc định liên kết tĩnh. Khi đó bất kỳ thư viện native nào cũng có thể là thủ phạm.

## Cây quyết định: từ tên được nêu tới cách sửa

Xét các dòng theo thứ tự. Dòng đầu tiên khớp với trường hợp của bạn sẽ cho bạn hướng đi.

| Bạn thấy gì                 | Tìm ở đâu              | Cách sửa                   |
| --------------------------- | ---------------------- | -------------------------- |
| Tên dạng `$s` hoặc `_$s`    | `xcrun swift-demangle` | Đi lại cây từ đầu          |
| Không có trong IPA của bạn  | `grep` trên bundle     | Gửi lại build              |
| Tên công khai trên iOS      | Header của iOS SDK     | Gửi lại build              |
| `API_UNAVAILABLE(ios)`      | Header của iOS SDK     | Cập nhật package           |
| Trong `Frameworks/X`        | SDK cung cấp X         | Cập nhật SDK               |
| Selector, app của bạn       | Pod và module native   | Thêm tiền tố hoặc cập nhật |
| Symbol `_name`, app của bạn | `nm -m`, rồi các pod   | Thay lời gọi               |
| Lớp, app của bạn            | Lớp của bạn và các pod | Thêm tiền tố cho lớp       |

Các dòng "Gửi lại build" báo hiệu một false positive, được bàn ở phần dưới.

## Bước 1: mở build đã gửi

Hãy làm việc trên file thực sự đã gửi cho Apple, không phải trên project của bạn. Tải IPA từ trang build trên expo.dev, rồi chạy trên Mac:

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

Dòng thứ hai liệt kê các framework được nhúng. Dòng thứ ba tìm liên kết trực tiếp tới một framework riêng tư của Apple: nó không được in ra gì cả. Trên Solys, `otool -L` không tìm thấy framework riêng tư nào, cả trong file thực thi lẫn trong 14 framework.

## Bước 2: tìm file chứa cái tên

Bắt đầu rộng. Tìm cái tên được nêu, bỏ các dấu gạch dưới ở đầu, trong toàn bộ bundle:

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

Lệnh này liệt kê các file chứa đoạn văn bản đó. Sau đó thu hẹp theo loại tên (selector, symbol, lớp), trên file tìm được, ký hiệu là `Binaire`:

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

**Với selector, dùng `strings`, không dùng `nm`.** Trên framework React của Solys, `nm` hiển thị phương thức kèm lớp của nó, như `-[RCTRootView initWithBridge:moduleName:initialProperties:]`, không bao giờ hiển thị riêng selector `initWithBridge:`. `strings` thì tìm được.

Với symbol, hãy đọc phần cuối dòng. Trên Solys, `nm -m` hiển thị ví dụ `(undefined) external __dyld_get_image_name (from libSystem)`: binary gọi hàm này mà không chứa nó, và libSystem cung cấp nó. Hàm này là công khai, như bước tiếp theo cho thấy.

## Bước 3: kiểm tra xem cái tên có thực sự riêng tư không

Header của iOS SDK mô tả các API công khai. Một lần tìm kiếm vài giây sẽ cho biết cái tên có nằm trong đó không, và trên những nền tảng nào. Với symbol, trước tiên bỏ dấu gạch dưới được thêm khi biên dịch: `_SCDynamicStoreCreate` trở thành `SCDynamicStoreCreate`.

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

Ngày 15 tháng 10 năm 2026, với iOS SDK 26.2, đây là kết quả cho bốn cái tên lấy từ các email ITMS-90338 thật:

| Tên được nêu                  | Trong iOS SDK         | Ý nghĩa                 |
| ----------------------------- | --------------------- | ----------------------- |
| \_dyld_get_shared_cache_range | Không có              | API riêng tư            |
| boldFontOfSize:               | Không có              | Riêng tư hoặc trùng tên |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios)  | Chỉ có trên macOS       |
| isMainFrame                   | Thuộc tính của WebKit | Công khai               |

**Một cái tên có mặt và khả dụng trên iOS chỉ ra false positive. Một cái tên không có mặt chỉ ra API riêng tư hoặc trùng tên.** Hai giới hạn: một API công khai chỉ dành cho Swift sẽ không xuất hiện trong các header này, và với một accessor như `setDelegate:`, hãy tìm thuộc tính `delegate`.

## Bước 4: lần ngược tới package

Nếu đường dẫn nêu một framework, tên của nó dẫn tới pod, rồi tới package npm:

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

`npm explain` cho biết dependency nào đã cài nó, nếu bạn chưa bao giờ tự thêm nó.

Nếu đường dẫn nêu file thực thi, hãy tìm cái tên trong mã nguồn native, rồi trong các pod:

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

Điều chỉnh danh sách thư mục: `modules` là thư mục chứa các module Expo cục bộ. Thư mục `ios/Pods` xuất hiện sau khi chạy `npx expo prebuild -p ios`, lệnh này cũng cài pod trên Mac. Lưu ý: nó có thể tạo lại thư mục `ios`. Nếu bạn đã sửa tay thư mục này, hãy làm việc trên một bản sao.

**Tìm trong `ios/Pods` quan trọng ngang với tìm trong mã nguồn.** Trên Solys, thư mục này chứa 15 framework được cung cấp sẵn ở dạng đã biên dịch, định dạng `.xcframework`: module Expo, Hermes, React Native và SDK bên thứ ba. Chỉ tìm trong file mã nguồn sẽ bỏ sót code của chúng.

## Các nguyên nhân thường gặp

**Một SDK bên thứ ba, quá cũ hoặc quá mới.** Tháng 9 năm 2026, một lập trình viên Flutter đã [báo cáo bị từ chối do một SDK bảo mật](https://github.com/talsec/Free-RASP-Flutter/issues/236): phiên bản 8.2.3 của nó gọi `__dyld_get_shared_cache_range`. Phiên bản 8.2.2 thì qua được, và nhà phát hành khuyên quay về bản đó trong lúc chờ bản sửa.

**Một module native gọi API không được tài liệu hóa.** Module của chính bạn, một plugin hay một thư viện nhỏ có thể dùng một phương thức nội bộ của UIKit tìm được trên diễn đàn nào đó. Bước kiểm tra phát hiện nó qua tên.

**Trùng tên.** Phương thức hoặc lớp của bạn tình cờ mang tên của một API riêng tư của Apple. Thông báo đã tính đến trường hợp này và yêu cầu bạn đổi tên. Apple khuyên nên [thêm tiền tố cho các phương thức bạn thêm vào lớp của Apple](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), ví dụ ba chữ cái viết thường theo sau là một dấu gạch dưới.

**Một API công khai trên macOS, nhưng không phải trên iOS.** Năm 2024, một app .NET MAUI đã [bị từ chối vì `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple có tài liệu cho hàm này, nhưng [chỉ dành cho macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), và header của iOS SDK đánh dấu nó là `API_UNAVAILABLE(ios, tvos, watchos)`.

## Sửa cho đúng, không lách

1. **Cập nhật dependency.** Với các package được Expo theo dõi, `npx expo install --check` báo những package không khớp với SDK của bạn, theo [tài liệu của Expo CLI](https://docs.expo.dev/more/expo-cli/). Với các package khác, hãy đọc changelog.
2. **Quay về phiên bản trước** nếu chính phiên bản mới đã đưa cái tên vào, và báo cho nhà phát hành.
3. **Thay thế hoặc gỡ bỏ** một thư viện đã bị bỏ rơi.
4. **Đổi tên** phương thức hoặc lớp của bạn, kèm tiền tố.

**Đừng che giấu cái tên.** Cắt nhỏ chuỗi hay gọi phương thức qua đường vòng chỉ giấu việc sử dụng chứ không sửa được nó. Việc đó vẫn bị cấm theo quy tắc 2.5.1 và theo thỏa thuận, và khi đó chính tài khoản của bạn gặp rủi ro.

Sau đó, build lại với một [số build](https://devoria.fr/vi/blog/build-number-app-store-connect-version) mới. Một bản cập nhật từ xa là không đủ: cái tên nằm trong code native đã biên dịch. Trước khi gửi, chạy lại bước 2 trên IPA mới. Cái tên không được xuất hiện nữa.

## Khi đó là false positive

Bước kiểm tra của Apple đôi khi sai. Ngày 6 tháng 6 năm 2022, các app React Native và Flutter đã nhận ITMS-90338 vì các tên công khai của WebKit, như `setNavigationDelegate:` hay `isMainFrame`, kể cả với những bản cập nhật gần như không thay đổi. Hôm sau, một kỹ sư của Apple viết rằng vấn đề đã được khắc phục ở phía kiểm tra và mời mọi người gửi lại build. Luồng thảo luận vẫn còn trên [kho GitHub của Expo](https://github.com/expo/expo/issues/17768).

Ba dấu hiệu cùng lúc chỉ ra một false positive:

- build giống với build được chấp nhận gần nhất;
- bước 2 không tìm thấy cái tên, hoặc bước 3 cho thấy nó công khai trên iOS;
- các lập trình viên khác báo cùng lỗi trong cùng ngày trên diễn đàn của Apple.

Trong trường hợp này, hãy xem [trạng thái dịch vụ của Apple](https://developer.apple.com/system-status/), rồi gửi lại build với số mới. Nếu lỗi vẫn còn, hãy viết cho [bộ phận hỗ trợ lập trình viên của Apple](https://developer.apple.com/contact/) kèm toàn bộ email. Còn trùng tên thì dưới góc nhìn của Apple không phải false positive: hãy đổi tên.

## Trước lần gửi tiếp theo

Các lỗi ITMS khác cũng có hướng dẫn riêng: [ITMS-90725](https://devoria.fr/vi/blog/itms-90725-sdk-version-issue-expo) cho phiên bản Xcode, [ITMS-91053](https://devoria.fr/vi/blog/sua-loi-itms-91053-missing-api-declaration-expo) cho privacy manifest và [ITMS-90034](https://devoria.fr/vi/blog/itms-90034-missing-invalid-signature) cho chữ ký. Khi build đã được chấp nhận, hãy cài nó qua [TestFlight](https://devoria.fr/vi/blog/testflight-kiem-thu-app-iphone) trước khi gửi duyệt.

Các lần bị reviewer từ chối cần nhìn nhận kỹ hơn: quy trình xử lý nằm ở trang [app bị Apple từ chối](https://devoria.fr/vi/expertise/ung-dung-bi-apple-tu-choi), và các lý do phổ biến nhất nằm trong bài [vì sao Apple từ chối app](https://devoria.fr/vi/blog/apple-tu-choi-app-5-ly-do-thuc-te).

Để kiểm tra hồ sơ trước khi gửi, [Review Kit](https://devoria.fr/vi/kits/app-review) gom checklist của mình và các skill Claude Code đọc lại project của bạn. Nếu việc gửi bị kẹt và bạn muốn có người xem giúp, [đặt một giờ với mình](https://devoria.fr/vi/coaching): mình cùng bạn xem lại project qua video call, ngay trên màn hình của bạn. Và nếu bạn đang chuẩn bị ra mắt app đầu tiên, [làm bài chẩn đoán miễn phí](https://devoria.fr/vi/diagnostic): nó sắp xếp lại các bước theo thứ tự và gửi bạn checklist chống bị Apple từ chối của mình.

## Câu hỏi thường gặp

### ITMS-90338 có phải là bị App Review từ chối không?

Không. Đây là bước kiểm tra tự động trong lúc xử lý build, trước mọi khâu duyệt thủ công. Build bị từ chối không vào được TestFlight cũng như không được đưa đi duyệt. Còn một API riêng tư bị reviewer phát hiện sau đó thì thuộc guideline 2.5.1.

### Có thể sửa ITMS-90338 bằng bản cập nhật từ xa không?

Không. Các tên được nêu nằm trong code native đã biên dịch của binary. Bản cập nhật từ xa chỉ thay đổi JavaScript và tài nguyên. Hãy sửa dependency, build lại rồi gửi lại.

### Vì sao Apple nêu những phương thức mà mình chưa từng viết?

Vì chúng đến từ một thư viện được nhúng, hoặc do trùng tên: một dependency khai báo phương thức mang tên của một API riêng tư. Apple đọc các tên có trong binary, không phải mã nguồn của bạn. Hãy lần ngược tới package bằng `grep` và `npm explain`.

### Làm sao biết một API có công khai trên iOS không?

Tìm tên của nó trong header của iOS SDK, với đường dẫn lấy từ `xcrun --sdk iphoneos --show-sdk-path`, rồi xem mức độ khả dụng. Trang tài liệu của Apple cũng ghi rõ điều này. Một hàm chỉ được tài liệu hóa cho macOS thì không công khai trên iPhone.

### Có cần đổi số build sau khi sửa không?

Có. Hãy để EAS tự tăng số: một số đã gửi cho cùng phiên bản có thể bị từ chối với lỗi ITMS-90189.


## Dự án của bạn

[Review Kit](https://devoria.fr/vi/kits/app-review): Checklist duyệt App Store của mình, các lần Apple từ chối đã sửa, checklist Google Play và ba skill Claude Code. 1.399.000 ₫.

[iOS & Android Kit](https://devoria.fr/vi/kits/ios): Tạo app iPhone và Android với Claude Code: code khởi đầu từ các app của mình, lộ trình đến tận store và tất cả các kit. 2.799.000 ₫.

[Chẩn đoán: điều gì ngăn bạn có app iPhone](https://devoria.fr/vi/diagnostic): 7 câu hỏi, 60 giây: bạn biết điều gì thật sự cản bạn đăng app iPhone, lộ trình 4 bước và kit phù hợp với bạn.
