ITMS-90338: corregir «Non-public API usage» amb Expo
Per Mehdi Azizi
Publicat el , 9 min
ITMS-90338: el teu build cita una API que Apple considera privada. Troba la dependència culpable, actualitza-la i torna a fer un build.
ITMS-90338 és un control automàtic d'Apple sobre el teu build. Diu que el teu binari fa referència a API que Apple considera privades: mètodes Objective-C, funcions o classes. La correcció: localitzar el fitxer citat al correu, arribar fins a la dependència que conté el nom, actualitzar-la o canviar el nom del teu mètode, i després tornar a fer un build.
Amb Expo, el nom gairebé sempre ve d'una biblioteca nativa, rarament del teu JavaScript. El 15 d'octubre de 2026 vaig executar les ordres d'anàlisi d'aquesta guia sobre un build de simulador de Solys, el meu despertador amb minijocs (SDK Expo 57), i sobre el SDK d'iOS de Xcode 26.2. Així es llegeix el correu, es troba el culpable i es reconeix un fals positiu.
Què vol dir l'error ITMS-90338?
El missatge es titula «ITMS-90338: Non-public API usage». N'hi ha tres variants, segons el que Apple hagi trobat al teu binari:
- «references non-public selectors»: noms de mètodes Objective-C, com
boldFontOfSize:; - «references non-public symbols»: funcions, escrites amb un guió baix al davant, com
__dyld_get_shared_cache_range; - «contains or inherits from non-public classes»: un nom de classe.
Cada variant dona un camí i després la llista de noms. El text afegeix dos consells: canviar el nom dels teus mètodes si coincideix amb una API privada, i treure la biblioteca estàtica que els conté. Un correu de setembre de 2026 també proposa xcrun swift-demangle per fer llegibles els noms Swift codificats.
La regla darrere d'aquest control és la guideline 2.5.1: una app només pot fer servir API públiques. El contracte de llicència de l'Apple Developer Program ho repeteix a l'article 3.3.1: cap crida a una API privada. La revisió humana d'aquesta regla es detalla a la guia de la guideline 2.5.1. Aquí tractem l'error d'enviament.
Com els altres errors ITMS, ningú no ha obert la teva app: Apple ha analitzat el binari mentre processava el build. Apple no publica cap llista d'API privades: els noms del teu correu són l'única llista fiable.
Llegir el camí: un SDK o el teu executable?
El camí escrit abans de la llista et diu en quin fitxer has de buscar.
Frameworks/Nom.framework/Nom: el nom és dins d'un framework dinàmic inclòs, per tant en un SDK concret. Al build de Solys, aquesta carpeta conté 14 frameworks: Hermes, React Native, mòduls d'Expo ja compilats i la biblioteca d'imatges SDWebImage.- Només el nom de la teva app: el nom és a l'executable principal. Amb Expo, aquest reuneix el teu codi natiu i els pods compilats des del codi font, enllaçats en estàtic per defecte. Qualsevol biblioteca nativa pot ser-ne la causa.
L'arbre: del nom citat a la correcció
Agafa les files per ordre. La primera que encaixi amb el teu cas et dona la pista.
| El que llegeixes | On buscar | Correcció |
|---|---|---|
Nom en $s o _$s | xcrun swift-demangle | Tornar a l'arbre |
| No apareix a la teva IPA | grep sobre el bundle | Tornar a enviar un build |
| Nom públic a iOS | Capçaleres del SDK d'iOS | Tornar a enviar un build |
API_UNAVAILABLE(ios) | Capçaleres del SDK d'iOS | Actualitzar el paquet |
A Frameworks/X | El SDK que proporciona X | Actualitzar el SDK |
| Selector, la teva app | Pods i mòduls natius | Prefixar o actualitzar |
Símbol _nom, la teva app | nm -m, després els pods | Substituir la crida |
| Classe, la teva app | Les teves classes i pods | Prefixar la classe |
Les files «Tornar a enviar un build» indiquen un fals positiu, que tracto més avall.
Pas 1: obrir el build enviat
Treballa sobre el fitxer que realment has enviat a Apple, no sobre el projecte. Descarrega l'IPA des de la pàgina del build a expo.dev i executa al teu Mac:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
La segona línia llista els frameworks inclosos. La tercera busca un enllaç directe a un framework privat d'Apple: no ha de mostrar res. A Solys, otool -L no troba cap framework privat, ni a l'executable ni als 14 frameworks.
Pas 2: trobar el fitxer que conté el nom
Comença ampli. Busca el nom citat, sense els guions baixos inicials, a tot el bundle:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
L'ordre llista els fitxers que contenen aquest text. Després afina segons el tipus de nom (selector, símbol, classe), sobre el fitxer trobat, anomenat Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Per a un selector, fes servir strings, no nm. Al framework React de Solys, nm mostra mètodes amb la seva classe, com -[RCTRootView initWithBridge:moduleName:initialProperties:], mai el selector initWithBridge: sol. strings sí que el troba.
Per a un símbol, llegeix el final de la línia. A Solys, nm -m mostra per exemple (undefined) external __dyld_get_image_name (from libSystem): el binari crida la funció sense contenir-la, i libSystem la proporciona. Aquesta funció és pública, com mostra el pas següent.
Pas 3: comprovar si el nom és realment privat
Les capçaleres del SDK d'iOS descriuen les API públiques. Una cerca de pocs segons et diu si el nom citat hi apareix, i en quines plataformes. Per a un símbol, treu primer el guió baix afegit en compilar: _SCDynamicStoreCreate passa a ser SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
El 15 d'octubre de 2026, amb el SDK d'iOS 26.2, aquest és el resultat per a quatre noms trets de correus ITMS-90338 reals:
| Nom citat | Al SDK d'iOS | Lectura |
|---|---|---|
| _dyld_get_shared_cache_range | Absent | API privada |
| boldFontOfSize: | Absent | Privada o col·lisió |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Només macOS |
| isMainFrame | Propietat de WebKit | Pública |
Un nom present i disponible a iOS apunta a un fals positiu. Un nom absent apunta a una API privada o a una col·lisió. Dos límits: una API pública només en Swift no apareix en aquestes capçaleres, i per a un accessor com setDelegate:, busca la propietat delegate.
Pas 4: arribar fins al paquet
Si el camí cita un framework, el seu nom porta al pod i després al paquet npm:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain et diu quina dependència l'ha instal·lat, si no l'has afegit mai tu.
Si el camí cita l'executable, busca el nom a les fonts natives i després als pods:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Adapta la llista de carpetes: modules és la dels mòduls d'Expo locals. La carpeta ios/Pods apareix després de npx expo prebuild -p ios, que també instal·la els pods al Mac. Compte: pot tornar a crear la carpeta ios. Si l'has modificat a mà, treballa sobre una còpia.
La cerca a ios/Pods compta tant com la de les fonts. A Solys, aquesta carpeta conté 15 frameworks ja compilats, en format .xcframework: mòduls d'Expo, Hermes, React Native i SDK de tercers. Una cerca limitada als fitxers font no veuria el seu codi.
Les causes típiques
Un SDK de tercers, massa antic o massa recent. El setembre de 2026, un desenvolupador de Flutter va informar d'un rebuig causat per un SDK de seguretat: la versió 8.2.3 cridava __dyld_get_shared_cache_range. La 8.2.2 passava, i l'editor va recomanar tornar-hi mentre esperaven una correcció.
Un mòdul natiu que crida una API no documentada. El teu propi mòdul, un plugin o una biblioteca petita pot fer servir un mètode intern d'UIKit trobat en un fòrum. El control el detecta pel nom.
Una col·lisió de nom. El teu mètode o la teva classe té, per casualitat, el nom d'una API privada d'Apple. El missatge ho preveu i et demana que el canviïs. Apple recomana prefixar els mètodes afegits a les seves classes, per exemple amb tres lletres minúscules seguides d'un guió baix.
Una API pública a macOS, però no a iOS. El 2024, una app .NET MAUI va ser rebutjada per _SCDynamicStoreCreate. Apple documenta aquesta funció, però només per a macOS, i la capçalera del SDK d'iOS la marca API_UNAVAILABLE(ios, tvos, watchos).
Corregir sense fer trampes
- Actualitza la dependència. Per als paquets que segueix Expo,
npx expo install --checkassenyala els que no coincideixen amb el teu SDK, segons la documentació de l'Expo CLI. Per als altres, llegeix el registre de versions. - Torna a la versió anterior si és la nova la que ha introduït el nom, i avisa l'editor.
- Substitueix o elimina una biblioteca abandonada.
- Canvia el nom del teu mètode o de la teva classe, amb un prefix.
No amaguis el nom. Trossejar una cadena o cridar el mètode per una via indirecta amaga l'ús sense corregir-lo. Continua prohibit per la regla 2.5.1 i pel contracte, i llavors és el teu compte el que queda exposat.
Després, torna a fer un build amb un nou número de build. Una actualització remota no n'hi ha prou: el nom és al codi natiu compilat. Abans d'enviar-lo, repeteix el pas 2 sobre la nova IPA. El nom ja no hi ha d'aparèixer.
Quan és un fals positiu
El control d'Apple de vegades s'equivoca. El 6 de juny de 2022, apps de React Native i Flutter van rebre ITMS-90338 per noms públics de WebKit, com setNavigationDelegate: o isMainFrame, fins i tot en actualitzacions gairebé sense canvis. L'endemà, un enginyer d'Apple va escriure que el problema estava resolt a la validació i va convidar a tornar a enviar els builds. El fil es conserva al repositori GitHub d'Expo.
Tres indicis junts apunten a un fals positiu:
- el build s'assembla a l'últim build acceptat;
- el pas 2 no troba el nom, o el pas 3 el mostra públic a iOS;
- altres desenvolupadors informen del mateix error el mateix dia als fòrums d'Apple.
En aquest cas, mira l'estat dels serveis d'Apple i torna a enviar un build amb un número nou. Si l'error persisteix, escriu al suport per a desenvolupadors d'Apple amb el correu complet. Una col·lisió de nom, en canvi, no és un fals positiu als ulls d'Apple: canvia el nom.
Abans del teu proper enviament
Els altres errors ITMS també tenen la seva guia: ITMS-90725 per a la versió de Xcode, ITMS-91053 per al manifest de privacitat i ITMS-90034 per a la signatura. Un cop acceptat el build, instal·la'l amb TestFlight abans de la revisió.
Els rebutjos d'un revisor demanen més perspectiva: el procediment és a la pàgina app rebutjada per Apple, i els motius més freqüents són a per què Apple rebutja una app.
Per revisar el teu expedient abans d'enviar-lo, el Review Kit reuneix la meva checklist i skills de Claude Code que revisen el teu projecte. Si el teu enviament es bloqueja i vols una mirada humana, reserva una hora amb mi: repassem el teu projecte per videotrucada, a la teva pantalla. I si prepares el teu primer llançament, fes el teu diagnòstic gratuït: posa els passos en ordre i t'envia la meva checklist antirebuig d'Apple.
Preguntes freqüents
ITMS-90338 és un rebuig de l'App Review?
No. És un control automàtic fet mentre es processa el build, abans de qualsevol revisió humana. El build rebutjat no arriba ni a TestFlight ni a revisió. Una API privada detectada més tard per un revisor depèn, en canvi, de la guideline 2.5.1.
Es pot corregir ITMS-90338 amb una actualització remota?
No. Els noms citats són al codi natiu compilat del binari. Una actualització remota només canvia el JavaScript i els recursos. Corregeix la dependència, torna a fer un build i envia'l de nou.
Per què Apple cita mètodes que no he escrit mai?
Perquè vénen d'una biblioteca inclosa, o d'una col·lisió: una dependència declara un mètode amb el nom d'una API privada. Apple llegeix els noms presents al binari, no el teu codi font. Arriba fins al paquet amb grep i npm explain.
Com sé si una API és pública a iOS?
Busca'n el nom a les capçaleres del SDK d'iOS, el camí de les quals et dona xcrun --sdk iphoneos --show-sdk-path, i mira'n la disponibilitat. La pàgina de documentació d'Apple també ho indica. Una funció documentada només per a macOS no és pública a l'iPhone.
Cal canviar el número de build després de la correcció?
Sí. Deixa que EAS l'incrementi: un número ja enviat per a la mateixa versió pot ser rebutjat amb ITMS-90189.

