ITMS-90338: betulkan “Non-public API usage” dengan Expo
Oleh Mehdi Azizi
Diterbitkan pada , 9 min
ITMS-90338: build anda menyebut kaedah, fungsi atau kelas yang Apple anggap peribadi. Cari dependency puncanya, kemas kininya, kemudian buat build semula.
ITMS-90338 ialah semakan automatik Apple pada build anda. Ia bermaksud binari anda merujuk API yang Apple anggap peribadi (private): kaedah Objective-C, fungsi atau kelas. Cara membetulkannya: kenal pasti fail yang disebut dalam e-mel, jejak hingga ke dependency yang mengandungi nama itu, kemas kini dependency tersebut atau namakan semula kaedah anda, kemudian buat build semula.
Dengan Expo, nama itu hampir selalu datang daripada library native, jarang sekali daripada JavaScript anda. Pada 15 Oktober 2026, saya menjalankan arahan analisis dalam panduan ini pada build simulator Solys, aplikasi jam penggera saya yang ada permainan mini (Expo SDK 57), dan pada iOS SDK Xcode 26.2. Berikut cara membaca e-mel itu, mencari puncanya dan mengenal pasti positif palsu.
Apakah maksud ralat ITMS-90338?
Mesejnya bertajuk "ITMS-90338: Non-public API usage". Ia ada tiga variasi, bergantung pada apa yang Apple temui dalam binari anda:
- "references non-public selectors": nama kaedah Objective-C, seperti
boldFontOfSize:; - "references non-public symbols": fungsi, ditulis dengan garis bawah di hadapan, seperti
__dyld_get_shared_cache_range; - "contains or inherits from non-public classes": nama kelas.
Setiap variasi memberikan satu laluan (path), diikuti senarai nama. Teksnya menambah dua nasihat: namakan semula kaedah anda jika namanya sama dengan API peribadi, dan buang library statik yang mengandunginya. Satu e-mel bulan September 2026 turut mencadangkan xcrun swift-demangle untuk menjadikan nama Swift yang dikodkan boleh dibaca.
Peraturan di sebalik semakan ini ialah guideline 2.5.1: aplikasi hanya boleh menggunakan API awam. Perjanjian lesen Apple Developer Program mengulanginya dalam fasal 3.3.1: tiada panggilan ke API peribadi. Semakan manusia bagi peraturan ini diterangkan dalam panduan guideline 2.5.1. Di sini, kita urus ralat semasa penghantaran.
Seperti ralat ITMS yang lain, tiada sesiapa membuka aplikasi anda: Apple menganalisis binari semasa memproses build. Apple tidak menerbitkan sebarang senarai API peribadi: nama dalam e-mel anda ialah satu-satunya senarai yang boleh dipercayai.
Membaca laluan: SDK atau executable anda?
Laluan yang ditulis sebelum senarai memberitahu anda fail mana yang perlu diperiksa.
Frameworks/Name.framework/Name: nama itu berada dalam framework dinamik yang dibenamkan, jadi dalam SDK tertentu. Pada build Solys, folder ini mengandungi 14 framework: Hermes, React Native, modul Expo yang dihantar siap dikompil dan library imej SDWebImage.- Nama aplikasi anda sahaja: nama itu berada dalam executable utama. Dengan Expo, ia menggabungkan kod native anda dan pod yang dikompil daripada sumbernya, dipautkan secara statik secara lalai. Mana-mana library native boleh jadi puncanya.
Pokok keputusan: daripada nama yang disebut kepada pembetulan
Ikut baris mengikut urutan. Baris pertama yang sepadan dengan kes anda memberi petunjuknya.
| Apa yang anda baca | Tempat mencari | Pembetulan |
|---|---|---|
Nama $s atau _$s | xcrun swift-demangle | Ulang pokok keputusan |
| Tiada dalam IPA anda | grep pada bundle | Hantar build semula |
| Nama awam di iOS | Header iOS SDK | Hantar build semula |
API_UNAVAILABLE(ios) | Header iOS SDK | Kemas kini pakej |
Dalam Frameworks/X | SDK yang menyediakan X | Kemas kini SDK |
| Selector, aplikasi anda | Pod dan modul native | Tambah awalan atau kemas kini |
Simbol _nama, aplikasi anda | nm -m, kemudian pod | Gantikan panggilan |
| Kelas, aplikasi anda | Kelas dan pod anda | Tambah awalan pada kelas |
Baris "Hantar build semula" menandakan positif palsu, yang dibincangkan di bawah.
Langkah 1: buka build yang dihantar
Gunakan fail yang benar-benar dihantar kepada Apple, bukan projek anda. Muat turun IPA dari halaman build di expo.dev, kemudian jalankan di Mac anda:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
Baris kedua menyenaraikan framework yang dibenamkan. Baris ketiga mencari pautan terus ke framework peribadi Apple: ia tidak sepatutnya memaparkan apa-apa. Pada Solys, otool -L tidak menemui sebarang framework peribadi, sama ada dalam executable atau dalam 14 framework tersebut.
Langkah 2: cari fail yang mengandungi nama itu
Mula secara luas. Cari nama yang disebut, tanpa garis bawah di hadapan, dalam seluruh bundle:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
Arahan ini menyenaraikan fail yang mengandungi teks tersebut. Kemudian perincikan mengikut jenis nama (selector, simbol, kelas), pada fail yang ditemui, ditulis sebagai Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Untuk selector, gunakan strings, bukan nm. Pada framework React Solys, nm memaparkan kaedah bersama kelasnya, seperti -[RCTRootView initWithBridge:moduleName:initialProperties:], tidak pernah selector initWithBridge: sahaja. strings dapat menemuinya.
Untuk simbol, baca hujung baris. Pada Solys, nm -m memaparkan contohnya (undefined) external __dyld_get_image_name (from libSystem): binari memanggil fungsi itu tanpa mengandunginya, dan libSystem yang menyediakannya. Fungsi ini awam, seperti yang ditunjukkan dalam langkah seterusnya.
Langkah 3: semak sama ada nama itu benar-benar peribadi
Header iOS SDK menerangkan API awam. Carian beberapa saat memberitahu sama ada nama yang disebut ada di situ, dan pada platform mana. Untuk simbol, buang dahulu garis bawah yang ditambah semasa 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 iOS SDK 26.2, inilah hasil bagi empat nama yang diambil daripada e-mel ITMS-90338 sebenar:
| Nama yang disebut | Dalam iOS SDK | Tafsiran |
|---|---|---|
| _dyld_get_shared_cache_range | Tiada | API peribadi |
| boldFontOfSize: | Tiada | Peribadi atau pertembungan nama |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | macOS sahaja |
| isMainFrame | Property WebKit | Awam |
Nama yang ada dan tersedia di iOS menunjukkan positif palsu. Nama yang tiada menunjukkan API peribadi atau pertembungan nama. Dua had: API awam yang hanya untuk Swift tidak muncul dalam header ini, dan untuk accessor seperti setDelegate:, cari property delegate.
Langkah 4: jejak hingga ke pakej
Jika laluan menyebut framework, namanya membawa kepada pod, kemudian kepada pakej npm:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain memberitahu dependency mana yang memasangnya, jika anda tidak pernah menambahnya sendiri.
Jika laluan menyebut executable, cari nama itu dalam sumber native, kemudian dalam pod:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Sesuaikan senarai folder: modules ialah folder untuk modul Expo tempatan. Folder ios/Pods muncul selepas npx expo prebuild -p ios, yang turut memasang pod di Mac. Awas: arahan ini boleh mencipta semula folder ios. Jika anda telah mengubahnya secara manual, gunakan salinan.
Carian dalam ios/Pods sama penting dengan carian dalam sumber. Pada Solys, folder ini mengandungi 15 framework yang dihantar siap dikompil, dalam format .xcframework: modul Expo, Hermes, React Native dan SDK pihak ketiga. Carian yang terhad kepada fail sumber akan terlepas kod mereka.
Punca biasa
SDK pihak ketiga, terlalu lama atau terlalu baharu. Pada September 2026, seorang pembangun Flutter melaporkan penolakan disebabkan SDK keselamatan: versi 8.2.3 memanggil __dyld_get_shared_cache_range. Versi 8.2.2 lulus, dan penerbitnya menasihatkan untuk kembali ke versi itu sementara menunggu pembetulan.
Modul native yang memanggil API tidak berdokumen. Modul anda sendiri, plugin atau library kecil mungkin menggunakan kaedah dalaman UIKit yang ditemui di forum. Semakan ini mengesannya melalui namanya.
Pertembungan nama. Kaedah atau kelas anda secara kebetulan mempunyai nama yang sama dengan API peribadi Apple. Mesej itu sudah menjangkakannya dan meminta anda menamakannya semula. Apple mengesyorkan awalan pada kaedah yang ditambah ke kelasnya, contohnya tiga huruf kecil diikuti garis bawah.
API awam di macOS, bukan di iOS. Pada 2024, sebuah aplikasi .NET MAUI ditolak kerana _SCDynamicStoreCreate. Apple memang mendokumenkan fungsi ini, tetapi untuk macOS sahaja, dan header iOS SDK menandakannya API_UNAVAILABLE(ios, tvos, watchos).
Betulkan tanpa memintas
- Kemas kini dependency. Untuk pakej yang dijejak oleh Expo,
npx expo install --checkmenandakan pakej yang tidak sepadan dengan SDK anda, menurut dokumentasi Expo CLI. Untuk yang lain, baca changelog. - Kembali ke versi sebelumnya jika versi baharu yang memperkenalkan nama itu, dan maklumkan kepada penerbitnya.
- Gantikan atau buang library yang sudah ditinggalkan.
- Namakan semula kaedah atau kelas anda, dengan awalan.
Jangan sembunyikan nama itu. Memecahkan string atau memanggil kaedah secara berlingkar hanya menyembunyikan penggunaannya tanpa membetulkannya. Ia tetap dilarang oleh peraturan 2.5.1 dan oleh perjanjian, dan akaun anda yang akan terdedah.
Kemudian, buat build semula dengan nombor build baharu. Kemas kini jarak jauh (OTA) tidak mencukupi: nama itu berada dalam kod native yang dikompil. Sebelum menghantar, jalankan semula langkah 2 pada IPA baharu. Nama itu tidak sepatutnya muncul lagi.
Apabila ia positif palsu
Semakan Apple kadangkala silap. Pada 6 Jun 2022, aplikasi React Native dan Flutter menerima ITMS-90338 untuk nama awam WebKit, seperti setNavigationDelegate: atau isMainFrame, termasuk pada kemas kini yang hampir tidak berubah. Keesokan harinya, seorang jurutera Apple menulis bahawa masalah itu telah diselesaikan di pihak pengesahan dan meminta build dihantar semula. Thread itu disimpan dalam repositori GitHub Expo.
Tiga petunjuk ini, jika semuanya ada, menunjukkan positif palsu:
- build itu serupa dengan build terakhir yang diterima;
- langkah 2 tidak menemui nama itu, atau langkah 3 menunjukkan ia awam di iOS;
- pembangun lain melaporkan ralat yang sama pada hari yang sama di forum Apple.
Dalam kes ini, semak status perkhidmatan Apple, kemudian hantar build semula dengan nombor baharu. Jika ralat berterusan, hubungi sokongan pembangun Apple dengan e-mel penuh. Pertembungan nama pula bukan positif palsu di mata Apple: namakan semula.
Sebelum penghantaran anda yang seterusnya
Ralat ITMS lain juga ada panduan masing-masing: ITMS-90725 untuk versi Xcode, ITMS-91053 untuk privacy manifest dan ITMS-90034 untuk tandatangan. Setelah build diterima, pasang ia dengan TestFlight sebelum semakan.
Penolakan oleh reviewer memerlukan lebih banyak pertimbangan: caranya ada di halaman aplikasi ditolak oleh Apple, dan sebab yang paling kerap ada dalam mengapa Apple menolak aplikasi.
Untuk menyemak fail anda sebelum menghantar, Review Kit menghimpunkan senarai semak saya dan skill Claude Code yang menyemak projek anda. Jika penghantaran anda tersekat dan anda mahukan pandangan manusia, tempah satu jam dengan saya: kita semak projek anda melalui panggilan video, di skrin anda. Dan jika anda sedang menyediakan pelancaran pertama, buat diagnostik percuma anda: ia menyusun langkah mengikut urutan dan menghantar senarai semak anti-penolakan Apple saya kepada anda.
Soalan lazim
Adakah ITMS-90338 penolakan oleh App Review?
Tidak. Ia semakan automatik yang dibuat semasa build diproses, sebelum sebarang semakan manusia. Build yang ditolak tidak sampai ke TestFlight mahupun ke semakan. API peribadi yang dikesan kemudian oleh reviewer pula tertakluk pada guideline 2.5.1.
Bolehkah ITMS-90338 dibetulkan dengan kemas kini jarak jauh?
Tidak. Nama yang disebut berada dalam kod native binari yang dikompil. Kemas kini jarak jauh hanya mengubah JavaScript dan aset. Betulkan dependency, buat build semula, kemudian hantar semula.
Mengapa Apple menyebut kaedah yang tidak pernah saya tulis?
Kerana ia datang daripada library yang dibenamkan, atau daripada pertembungan nama: satu dependency mengisytiharkan kaedah yang namanya sama dengan API peribadi. Apple membaca nama yang ada dalam binari, bukan kod sumber anda. Jejak hingga ke pakej dengan grep dan npm explain.
Bagaimana mengetahui sama ada API itu awam di iOS?
Cari namanya dalam header iOS SDK, yang laluannya diberikan oleh xcrun --sdk iphoneos --show-sdk-path, kemudian baca ketersediaannya. Halaman dokumentasi Apple juga menyatakannya. Fungsi yang didokumenkan untuk macOS sahaja bukan API awam di iPhone.
Perlukah nombor build ditukar selepas pembetulan?
Ya. Biarkan EAS menaikkannya: nombor yang sudah dihantar untuk versi yang sama boleh ditolak dengan ITMS-90189.

