# ITMS-90338: cara memperbaiki Non-public API usage di Expo

> ITMS-90338: build kamu menyebut method, fungsi, atau class yang dianggap privat oleh Apple. Temukan dependensi penyebabnya, perbarui, lalu build ulang.

Halaman asli: [ITMS-90338: cara memperbaiki Non-public API usage di Expo](https://devoria.fr/id/blog/itms-90338-non-public-api-usage-expo)
Penulis: [Mehdi Azizi](https://devoria.fr/id/mehdi-azizi)
Terbit: 2026-11-04
Revisi konten terakhir: 2026-11-04


ITMS-90338 adalah pemeriksaan otomatis Apple pada build kamu. Artinya, binary kamu merujuk ke API yang dianggap privat oleh Apple: method Objective-C, fungsi, atau class. **Cara memperbaikinya: cari file yang disebut di e-mail, telusuri sampai ke dependensi yang memuat nama itu, perbarui dependensinya atau ganti nama method kamu, lalu build ulang.**

Dengan Expo, nama itu hampir selalu berasal dari library native, jarang dari JavaScript kamu. Pada 15 Oktober 2026, saya menjalankan perintah analisis di panduan ini pada build simulator Solys, aplikasi alarm saya yang punya mini-game (Expo SDK 57), dan pada SDK iOS dari Xcode 26.2. Berikut cara membaca e-mailnya, menemukan biang keroknya, dan mengenali false positive.

## Apa arti error ITMS-90338?

Judul pesannya "ITMS-90338: Non-public API usage". Ada tiga variannya, tergantung apa yang ditemukan Apple di binary kamu:

- **"references non-public selectors"**: nama method Objective-C, seperti `boldFontOfSize:`;
- **"references non-public symbols"**: fungsi, ditulis dengan garis bawah di depannya, seperti `__dyld_get_shared_cache_range`;
- **"contains or inherits from non-public classes"**: nama class.

Setiap varian menyebutkan sebuah path, lalu daftar namanya. Teksnya juga memberi dua saran: ganti nama method kamu jika namanya sama dengan API privat, dan hapus static library yang memuatnya. Sebuah e-mail dari September 2026 juga menyarankan `xcrun swift-demangle` untuk membuat nama Swift yang ter-encode bisa dibaca.

Aturan di balik pemeriksaan ini adalah [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): aplikasi hanya boleh memakai API publik. [Perjanjian lisensi Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) menegaskannya lagi di pasal 3.3.1: tidak boleh ada panggilan ke API privat. Review manual untuk aturan ini dibahas di panduan [guideline 2.5.1](https://devoria.fr/id/blog/guideline-2-5-1-api-publik-sdk-expo). Di sini, kita membahas error saat upload.

Seperti error ITMS lainnya, tidak ada orang yang membuka aplikasimu: Apple menganalisis binary saat memproses build. **Apple tidak menerbitkan daftar API privat: nama-nama di e-mail kamu adalah satu-satunya daftar yang bisa dipercaya.**

## Membaca path: SDK atau executable kamu?

Path yang tertulis sebelum daftar memberi tahu file mana yang harus kamu periksa.

- **`Frameworks/Nom.framework/Nom`**: nama itu ada di framework dinamis yang di-embed, jadi di SDK tertentu. Di build Solys, folder ini berisi 14 framework: Hermes, React Native, modul Expo yang dikirim dalam bentuk sudah terkompilasi, dan library gambar SDWebImage.
- **Hanya nama aplikasimu**: nama itu ada di executable utama. Dengan Expo, executable ini menggabungkan kode native kamu dan pod yang dikompilasi dari source, yang secara default di-link secara statis. Jadi library native mana pun bisa jadi penyebabnya.

## Pohon keputusan: dari nama yang disebut ke perbaikan

Baca barisnya berurutan. Baris pertama yang cocok dengan kasusmu menunjukkan arahnya.

| Yang kamu baca               | Tempat mencari         | Perbaikan                  |
| ---------------------------- | ---------------------- | -------------------------- |
| Nama diawali `$s` atau `_$s` | `xcrun swift-demangle` | Ulangi dari atas           |
| Tidak ada di IPA kamu        | `grep` pada bundle     | Kirim ulang build          |
| Nama publik di iOS           | Header SDK iOS         | Kirim ulang build          |
| `API_UNAVAILABLE(ios)`       | Header SDK iOS         | Perbarui paketnya          |
| Di `Frameworks/X`            | SDK yang menyediakan X | Perbarui SDK-nya           |
| Selector, aplikasimu         | Pod dan modul native   | Beri prefiks atau perbarui |
| Simbol `_nom`, aplikasimu    | `nm -m`, lalu pod      | Ganti panggilannya         |
| Class, aplikasimu            | Class kamu dan pod     | Beri prefiks pada class    |

Baris "Kirim ulang build" menandakan false positive, dibahas di bawah.

## Langkah 1: buka build yang dikirim

Kerjakan pada file yang benar-benar dikirim ke Apple, bukan pada project kamu. Unduh IPA dari halaman build di expo.dev, lalu jalankan di Mac kamu:

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

Baris kedua menampilkan framework yang di-embed. Baris ketiga mencari link langsung ke framework privat Apple: hasilnya harus kosong. Di Solys, `otool -L` tidak menemukan framework privat apa pun, baik di executable maupun di 14 framework.

## Langkah 2: temukan file yang memuat nama itu

Mulai dari pencarian luas. Cari nama yang disebut, tanpa garis bawah di depannya, di seluruh bundle:

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

Perintah ini menampilkan file yang memuat teks tersebut. Lalu persempit sesuai jenis namanya (selector, simbol, class), pada file yang ditemukan, ditulis sebagai `Binaire`:

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

**Untuk selector, pakai `strings`, bukan `nm`.** Pada framework React di Solys, `nm` menampilkan method beserta class-nya, seperti `-[RCTRootView initWithBridge:moduleName:initialProperties:]`, tidak pernah selector `initWithBridge:` saja. `strings` bisa menemukannya.

Untuk simbol, baca akhir barisnya. Di Solys, `nm -m` misalnya menampilkan `(undefined) external __dyld_get_image_name (from libSystem)`: binary memanggil fungsi itu tanpa memuatnya, dan libSystem yang menyediakannya. Fungsi yang ini publik, seperti yang ditunjukkan langkah berikutnya.

## Langkah 3: cek apakah nama itu benar-benar privat

Header SDK iOS menjelaskan API publik. Pencarian beberapa detik sudah cukup untuk tahu apakah nama yang disebut ada di sana, dan di platform apa saja. Untuk simbol, hapus dulu garis bawah yang ditambahkan saat kompilasi: `_SCDynamicStoreCreate` menjadi `SCDynamicStoreCreate`.

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

Pada 15 Oktober 2026, dengan SDK iOS 26.2, inilah hasil untuk empat nama yang diambil dari e-mail ITMS-90338 asli:

| Nama yang disebut             | Di SDK iOS           | Artinya              |
| ----------------------------- | -------------------- | -------------------- |
| \_dyld_get_shared_cache_range | Tidak ada            | API privat           |
| boldFontOfSize:               | Tidak ada            | Privat atau tabrakan |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios) | Hanya macOS          |
| isMainFrame                   | Properti WebKit      | Publik               |

**Nama yang ada dan tersedia di iOS mengarah ke false positive. Nama yang tidak ada mengarah ke API privat atau tabrakan nama.** Ada dua batasan: API yang publik hanya di Swift tidak muncul di header ini, dan untuk accessor seperti `setDelegate:`, cari properti `delegate`.

## Langkah 4: telusuri sampai ke paketnya

Jika path menyebut sebuah framework, namanya akan membawamu ke pod, lalu ke paket npm:

```bash
grep -n "NomDuFramework" ios/Podfile.lock
npm explain nom-du-paquet
```

`npm explain` memberi tahu dependensi mana yang memasangnya, jika kamu tidak pernah menambahkannya sendiri.

Jika path menyebut executable, cari nama itu di source native, lalu di pod:

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

Sesuaikan daftar foldernya: `modules` adalah folder untuk modul Expo lokal. Folder `ios/Pods` muncul setelah `npx expo prebuild -p ios`, yang juga memasang pod di Mac. Hati-hati: perintah ini bisa membuat ulang folder `ios`. Jika kamu mengubahnya secara manual, kerjakan di salinannya.

**Pencarian di `ios/Pods` sama pentingnya dengan pencarian di source.** Di Solys, folder ini berisi 15 framework yang dikirim sudah terkompilasi, dalam format `.xcframework`: modul Expo, Hermes, React Native, dan SDK pihak ketiga. Pencarian yang hanya di file source akan melewatkan kode mereka.

## Penyebab yang umum

**SDK pihak ketiga yang terlalu lama atau terlalu baru.** Pada September 2026, seorang developer Flutter [melaporkan penolakan karena SDK keamanan](https://github.com/talsec/Free-RASP-Flutter/issues/236): versi 8.2.3-nya memanggil `__dyld_get_shared_cache_range`. Versi 8.2.2 lolos, dan pembuatnya menyarankan untuk kembali ke versi itu sambil menunggu perbaikan.

**Modul native yang memanggil API tak terdokumentasi.** Modul buatanmu sendiri, sebuah plugin, atau library kecil bisa memakai method internal UIKit yang ditemukan di forum. Pemeriksaan ini mendeteksinya lewat namanya.

**Tabrakan nama.** Method atau class kamu kebetulan punya nama yang sama dengan API privat Apple. Pesannya sudah mengantisipasi ini dan memintamu mengganti namanya. Apple menyarankan untuk [memberi prefiks pada method yang ditambahkan ke class-nya](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), misalnya tiga huruf kecil diikuti garis bawah.

**API yang publik di macOS, tapi tidak di iOS.** Pada 2024, sebuah aplikasi .NET MAUI [ditolak karena `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple memang mendokumentasikan fungsi ini, tapi [hanya untuk macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), dan header SDK iOS menandainya `API_UNAVAILABLE(ios, tvos, watchos)`.

## Memperbaiki tanpa mengakali

1. **Perbarui dependensinya.** Untuk paket yang dipantau Expo, `npx expo install --check` menandai paket yang tidak cocok dengan SDK kamu, menurut [dokumentasi Expo CLI](https://docs.expo.dev/more/expo-cli/). Untuk paket lain, baca changelog-nya.
2. **Kembali ke versi sebelumnya** jika versi baru yang memunculkan nama itu, dan beri tahu pembuatnya.
3. **Ganti atau hapus** library yang sudah ditinggalkan.
4. **Ganti nama** method atau class kamu, dengan prefiks.

**Jangan menyamarkan namanya.** Memecah string atau memanggil method lewat jalan memutar hanya menyembunyikan penggunaannya tanpa memperbaikinya. Itu tetap dilarang oleh aturan 2.5.1 dan oleh perjanjian, dan akun kamu yang jadi taruhannya.

Setelah itu, build ulang dengan [nomor build](https://devoria.fr/id/blog/build-number-app-store-connect-versi-nomor-build) baru. Update jarak jauh (OTA) tidak cukup: nama itu ada di kode native yang terkompilasi. Sebelum mengirim, jalankan lagi langkah 2 pada IPA yang baru. Nama itu tidak boleh muncul lagi.

## Kalau ternyata false positive

Pemeriksaan Apple kadang keliru. Pada 6 Juni 2022, aplikasi React Native dan Flutter menerima ITMS-90338 untuk nama publik WebKit, seperti `setNavigationDelegate:` atau `isMainFrame`, termasuk untuk update yang hampir tidak berubah. Keesokan harinya, seorang engineer Apple menulis bahwa masalahnya sudah diperbaiki di sisi validasi dan meminta developer mengirim ulang build mereka. Thread-nya tersimpan di [repositori GitHub Expo](https://github.com/expo/expo/issues/17768).

Tiga petunjuk ini, jika muncul bersamaan, mengarah ke false positive:

- build-nya mirip dengan build terakhir yang diterima;
- langkah 2 tidak menemukan nama itu, atau langkah 3 menunjukkan nama itu publik di iOS;
- developer lain melaporkan error yang sama di hari yang sama di forum Apple.

Dalam kasus ini, cek [status layanan Apple](https://developer.apple.com/system-status/), lalu kirim ulang build dengan nomor baru. Jika error-nya tetap muncul, hubungi [dukungan developer Apple](https://developer.apple.com/contact/) dengan e-mail lengkapnya. Tabrakan nama, di sisi lain, bukan false positive di mata Apple: ganti namanya.

## Sebelum pengiriman berikutnya

Error ITMS lainnya juga punya panduan sendiri: [ITMS-90725](https://devoria.fr/id/blog/itms-90725-sdk-version-issue-expo) untuk versi Xcode, [ITMS-91053](https://devoria.fr/id/blog/itms-91053-missing-api-declaration-expo) untuk privacy manifest, dan [ITMS-90034](https://devoria.fr/id/blog/itms-90034-missing-invalid-signature) untuk signature. Setelah build diterima, pasang lewat [TestFlight](https://devoria.fr/id/blog/testflight-cara-tes-aplikasi-iphone) sebelum review.

Penolakan dari reviewer butuh pendekatan yang lebih matang: langkah-langkahnya ada di halaman [aplikasi ditolak Apple](https://devoria.fr/id/expertise/aplikasi-ditolak-apple), dan alasan yang paling sering ada di [kenapa Apple menolak aplikasi](https://devoria.fr/id/blog/aplikasi-ditolak-apple-penyebab-solusi).

Untuk memeriksa berkasmu sebelum mengirim, [Review Kit](https://devoria.fr/id/kits/app-review) berisi checklist saya dan skill Claude Code yang memeriksa ulang project kamu. Kalau pengirimanmu macet dan kamu butuh pendapat manusia, [pesan sesi satu jam dengan saya](https://devoria.fr/id/coaching): kita bahas project kamu lewat video call, langsung di layarmu. Dan kalau kamu sedang menyiapkan rilis pertama, [ikuti diagnostik gratisnya](https://devoria.fr/id/diagnostic): urutan langkahnya akan dirapikan, dan kamu akan menerima checklist anti-penolakan Apple dari saya.

## Pertanyaan umum

### Apakah ITMS-90338 penolakan dari App Review?

Bukan. Ini pemeriksaan otomatis saat build diproses, sebelum review manual apa pun. Build yang ditolak tidak akan masuk ke TestFlight maupun ke review. API privat yang ditemukan reviewer belakangan masuk ke ranah guideline 2.5.1.

### Bisakah ITMS-90338 diperbaiki dengan update jarak jauh?

Tidak. Nama-nama yang disebut ada di kode native terkompilasi di dalam binary. Update jarak jauh hanya mengubah JavaScript dan aset. Perbaiki dependensinya, build ulang, lalu kirim lagi.

### Kenapa Apple menyebut method yang tidak pernah saya tulis?

Karena method itu berasal dari library yang di-embed, atau dari tabrakan nama: sebuah dependensi mendeklarasikan method yang namanya sama dengan API privat. Apple membaca nama yang ada di binary, bukan source code kamu. Telusuri sampai ke paketnya dengan `grep` dan `npm explain`.

### Bagaimana cara tahu apakah sebuah API publik di iOS?

Cari namanya di header SDK iOS, yang path-nya bisa didapat dari `xcrun --sdk iphoneos --show-sdk-path`, lalu baca ketersediaannya. Halaman dokumentasi Apple juga mencantumkannya. Fungsi yang didokumentasikan hanya untuk macOS bukan API publik di iPhone.

### Perlukah mengganti nomor build setelah perbaikan?

Ya. Biarkan EAS menaikkannya: nomor yang sudah pernah dikirim untuk versi yang sama bisa ditolak dengan ITMS-90189.


## Proyekmu

[Review Kit](https://devoria.fr/id/kits/app-review): Checklist review App Store saya, penolakan Apple yang sudah saya perbaiki, checklist Google Play, dan tiga skill Claude Code. Rp 999.000.

[iOS & Android Kit](https://devoria.fr/id/kits/ios): Bikin app iPhone dan Android kamu dengan Claude Code: kode awal dari app-app saya, langkah demi langkah sampai ke store, dan semua kit. Rp 1.999.000.

[Diagnosis: apa yang memisahkanmu dari app iPhone-mu](https://devoria.fr/id/diagnostic): 7 pertanyaan, 60 detik: kamu tahu apa yang benar-benar menghambatmu merilis app iPhone, jalurmu dalam 4 tahap, dan kit yang cocok untukmu.
