ITMS-90338: cara memperbaiki Non-public API usage di Expo
Oleh Mehdi Azizi
Terbit , 9 mnt
ITMS-90338: build kamu menyebut method, fungsi, atau class yang dianggap privat oleh Apple. Temukan dependensi penyebabnya, perbarui, lalu build ulang.
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: aplikasi hanya boleh memakai API publik. Perjanjian lisensi Apple Developer Program 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. 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:
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:
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:
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.
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:
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:
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: 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, 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. Apple memang mendokumentasikan fungsi ini, tapi hanya untuk macOS, dan header SDK iOS menandainya API_UNAVAILABLE(ios, tvos, watchos).
Memperbaiki tanpa mengakali
- Perbarui dependensinya. Untuk paket yang dipantau Expo,
npx expo install --checkmenandai paket yang tidak cocok dengan SDK kamu, menurut dokumentasi Expo CLI. Untuk paket lain, baca changelog-nya. - Kembali ke versi sebelumnya jika versi baru yang memunculkan nama itu, dan beri tahu pembuatnya.
- Ganti atau hapus library yang sudah ditinggalkan.
- 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 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.
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, lalu kirim ulang build dengan nomor baru. Jika error-nya tetap muncul, hubungi dukungan developer Apple 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 untuk versi Xcode, ITMS-91053 untuk privacy manifest, dan ITMS-90034 untuk signature. Setelah build diterima, pasang lewat TestFlight sebelum review.
Penolakan dari reviewer butuh pendekatan yang lebih matang: langkah-langkahnya ada di halaman aplikasi ditolak Apple, dan alasan yang paling sering ada di kenapa Apple menolak aplikasi.
Untuk memeriksa berkasmu sebelum mengirim, Review Kit 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: kita bahas project kamu lewat video call, langsung di layarmu. Dan kalau kamu sedang menyiapkan rilis pertama, ikuti diagnostik gratisnya: 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.

