ITMS-90338: correggere “Non-public API usage” con Expo
Di Mehdi Azizi
Pubblicato il , 9 min
ITMS-90338: il tuo build cita un metodo, una funzione o una classe che Apple considera privati. Trova la dipendenza colpevole, aggiornala e rifai il build.
ITMS-90338 è un controllo automatico di Apple sul tuo build. Ti dice che il tuo binario fa riferimento ad API che Apple considera private: metodi Objective-C, funzioni o classi. La soluzione: individuare il file citato nell'email, risalire alla dipendenza che contiene il nome, aggiornarla o rinominare il tuo metodo, poi rifare il build.
Con Expo, il nome arriva quasi sempre da una libreria nativa, raramente dal tuo JavaScript. Il 15 ottobre 2026 ho lanciato i comandi di analisi di questa guida su un build per simulatore di Solys, la mia sveglia con minigiochi (SDK Expo 57), e sull'SDK iOS di Xcode 26.2. Ecco come leggere l'email, trovare il colpevole e riconoscere un falso positivo.
Cosa significa l'errore ITMS-90338?
Il messaggio si intitola "ITMS-90338: Non-public API usage". Esiste in tre varianti, a seconda di cosa Apple ha trovato nel tuo binario:
- "references non-public selectors": nomi di metodi Objective-C, come
boldFontOfSize:; - "references non-public symbols": funzioni, scritte con un trattino basso davanti, come
__dyld_get_shared_cache_range; - "contains or inherits from non-public classes": un nome di classe.
Ogni variante riporta un percorso, poi l'elenco dei nomi. Il testo aggiunge due consigli: rinominare i tuoi metodi se il loro nome coincide con un'API privata, e rimuovere la libreria statica che li contiene. Un'email di settembre 2026 propone anche xcrun swift-demangle per rendere leggibili i nomi Swift codificati.
La regola dietro questo controllo è la guideline 2.5.1: un'app può usare solo API pubbliche. Il contratto di licenza dell'Apple Developer Program lo ribadisce all'articolo 3.3.1: nessuna chiamata a un'API privata. La revisione umana di questa regola è spiegata nella guida alla guideline 2.5.1. Qui ci occupiamo dell'errore di invio.
Come per gli altri errori ITMS, nessuno ha aperto la tua app: Apple ha analizzato il binario durante l'elaborazione del build. Apple non pubblica alcun elenco delle API private: i nomi nella tua email sono l'unico elenco affidabile.
Leggere il percorso: un SDK o il tuo eseguibile?
Il percorso scritto prima dell'elenco ti dice in quale file cercare.
Frameworks/Nome.framework/Nome: il nome si trova in un framework dinamico incluso, quindi in un SDK preciso. Nel build di Solys questa cartella contiene 14 framework: Hermes, React Native, moduli Expo forniti già compilati e la libreria di immagini SDWebImage.- Solo il nome della tua app: il nome si trova nell'eseguibile principale. Con Expo, questo riunisce il tuo codice nativo e i pod compilati dai sorgenti, collegati staticamente per impostazione predefinita. Qualsiasi libreria nativa può quindi essere la causa.
L'albero: dal nome citato alla correzione
Scorri le righe in ordine. La prima che corrisponde al tuo caso ti dà la pista.
| Cosa leggi | Dove cercare | Correzione |
|---|---|---|
Nome in $s o _$s | xcrun swift-demangle | Ripartire dall'albero |
| Introvabile nella tua IPA | grep sul bundle | Rinviare un build |
| Nome pubblico su iOS | Header dell'SDK iOS | Rinviare un build |
API_UNAVAILABLE(ios) | Header dell'SDK iOS | Aggiornare il pacchetto |
In Frameworks/X | L'SDK che fornisce X | Aggiornare l'SDK |
| Selettore, la tua app | Pod e moduli nativi | Prefissare o aggiornare |
Simbolo _nome, la tua app | nm -m, poi i pod | Sostituire la chiamata |
| Classe, la tua app | Le tue classi e i pod | Prefissare la classe |
Le righe "Rinviare un build" segnalano un falso positivo, trattato più sotto.
Passo 1: aprire il build inviato
Lavora sul file effettivamente inviato ad Apple, non sul tuo progetto. Scarica l'IPA dalla pagina del build su expo.dev, poi lancia sul tuo Mac:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
La seconda riga elenca i framework inclusi. La terza cerca un collegamento diretto a un framework privato di Apple: non deve mostrare nulla. Su Solys, otool -L non trova alcun framework privato, né nell'eseguibile né nei 14 framework.
Passo 2: trovare il file che contiene il nome
Parti largo. Cerca il nome citato, senza i trattini bassi iniziali, in tutto il bundle:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
Il comando elenca i file che contengono quel testo. Poi restringi in base al tipo di nome (selettore, simbolo, classe), sul file trovato, indicato come Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Per un selettore usa strings, non nm. Sul framework React di Solys, nm mostra i metodi con la loro classe, come -[RCTRootView initWithBridge:moduleName:initialProperties:], mai il selettore initWithBridge: da solo. strings invece lo trova.
Per un simbolo, leggi la fine della riga. Su Solys, nm -m mostra per esempio (undefined) external __dyld_get_image_name (from libSystem): il binario chiama la funzione senza contenerla, ed è libSystem a fornirla. Quella funzione è pubblica, come mostra il passo successivo.
Passo 3: verificare se il nome è davvero privato
Gli header dell'SDK iOS descrivono le API pubbliche. Una ricerca di pochi secondi ti dice se il nome citato è presente, e su quali piattaforme. Per un simbolo, togli prima il trattino basso aggiunto in compilazione: _SCDynamicStoreCreate diventa SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Il 15 ottobre 2026, con l'SDK iOS 26.2, ecco il risultato per quattro nomi presi da vere email ITMS-90338:
| Nome citato | Nell'SDK iOS | Lettura |
|---|---|---|
| _dyld_get_shared_cache_range | Assente | API privata |
| boldFontOfSize: | Assente | Privata o collisione |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Solo macOS |
| isMainFrame | Proprietà di WebKit | Pubblica |
Un nome presente e disponibile su iOS indica un falso positivo. Un nome assente indica un'API privata o una collisione. Due limiti: un'API pubblica solo in Swift non compare in questi header, e per un accessor come setDelegate: cerca la proprietà delegate.
Passo 4: risalire al pacchetto
Se il percorso cita un framework, il suo nome porta al pod, poi al pacchetto npm:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain ti dice quale dipendenza l'ha installato, se non l'hai mai aggiunto tu.
Se il percorso cita l'eseguibile, cerca il nome nei sorgenti nativi, poi nei pod:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Adatta l'elenco delle cartelle: modules è quella dei moduli Expo locali. La cartella ios/Pods compare dopo npx expo prebuild -p ios, che su Mac installa anche i pod. Attenzione: può ricreare la cartella ios. Se l'hai modificata a mano, lavora su una copia.
La ricerca in ios/Pods conta quanto quella nei sorgenti. Su Solys, questa cartella contiene 15 framework forniti già compilati, in formato .xcframework: moduli Expo, Hermes, React Native e SDK di terze parti. Una ricerca limitata ai file sorgente non vedrebbe il loro codice.
Le cause tipiche
Un SDK di terze parti, troppo vecchio o troppo recente. A settembre 2026 uno sviluppatore Flutter ha segnalato un rifiuto dovuto a un SDK di sicurezza: la versione 8.2.3 chiamava __dyld_get_shared_cache_range. La versione 8.2.2 passava, e l'editore ha consigliato di tornarci in attesa di una correzione.
Un modulo nativo che chiama un'API non documentata. Il tuo modulo, un plugin o una piccola libreria può usare un metodo interno di UIKit trovato su un forum. Il controllo lo individua dal nome.
Una collisione di nomi. Il tuo metodo o la tua classe ha per caso il nome di un'API privata di Apple. Il messaggio lo prevede e ti chiede di rinominarlo. Apple consiglia di prefissare i metodi aggiunti alle sue classi, per esempio con tre lettere minuscole seguite da un trattino basso.
Un'API pubblica su macOS, non su iOS. Nel 2024 un'app .NET MAUI è stata rifiutata per _SCDynamicStoreCreate. Apple documenta questa funzione, ma solo per macOS, e l'header dell'SDK iOS la marca API_UNAVAILABLE(ios, tvos, watchos).
Correggere senza aggirare
- Aggiorna la dipendenza. Per i pacchetti seguiti da Expo,
npx expo install --checksegnala quelli che non corrispondono al tuo SDK, secondo la documentazione dell'Expo CLI. Per gli altri, leggi il changelog. - Torna alla versione precedente se è quella nuova ad aver introdotto il nome, e avvisa l'editore.
- Sostituisci o rimuovi una libreria abbandonata.
- Rinomina il tuo metodo o la tua classe, con un prefisso.
Non mascherare il nome. Spezzare una stringa o chiamare il metodo per vie traverse nasconde l'uso senza correggerlo. Resta vietato dalla regola 2.5.1 e dal contratto, e a quel punto è il tuo account a essere esposto.
Poi rifai un build con un nuovo numero di build. Un aggiornamento da remoto non basta: il nome è nel codice nativo compilato. Prima dell'invio, ripeti il passo 2 sulla nuova IPA. Il nome non deve più comparire.
Quando è un falso positivo
Il controllo di Apple a volte sbaglia. Il 6 giugno 2022 alcune app React Native e Flutter hanno ricevuto ITMS-90338 per nomi pubblici di WebKit, come setNavigationDelegate: o isMainFrame, anche su aggiornamenti quasi invariati. Il giorno dopo un ingegnere di Apple ha scritto che il problema era stato risolto lato validazione e ha invitato a rinviare i build. La discussione è conservata nel repository GitHub di Expo.
Tre indizi messi insieme indicano un falso positivo:
- il build somiglia all'ultimo build accettato;
- il passo 2 non trova il nome, oppure il passo 3 lo mostra pubblico su iOS;
- altri sviluppatori segnalano lo stesso errore lo stesso giorno sui forum di Apple.
In questo caso controlla lo stato dei servizi Apple, poi rinvia un build con un nuovo numero. Se l'errore persiste, scrivi al supporto sviluppatori di Apple allegando l'email completa. Una collisione di nomi, invece, per Apple non è un falso positivo: rinomina.
Prima del tuo prossimo invio
Anche gli altri errori ITMS hanno la loro guida: ITMS-90725 per la versione di Xcode, ITMS-91053 per il privacy manifest e ITMS-90034 per la firma. Una volta accettato il build, installalo con TestFlight prima della revisione.
I rifiuti di un reviewer richiedono più distacco: il procedimento è nella pagina app rifiutata da Apple, e i motivi più frequenti sono in perché Apple rifiuta un'app.
Per controllare il tuo progetto prima dell'invio, il Review Kit riunisce la mia checklist e delle skill di Claude Code che rileggono il tuo progetto. Se il tuo invio è bloccato e vuoi uno sguardo umano, prenota un'ora con me: riprendiamo il tuo progetto in videochiamata, sul tuo schermo. E se stai preparando la tua prima pubblicazione, fai la diagnosi gratuita: rimette in ordine i passaggi e ti invia la mia checklist anti-rifiuto Apple.
Domande frequenti
ITMS-90338 è un rifiuto dell'App Review?
No. È un controllo automatico fatto durante l'elaborazione del build, prima di qualsiasi revisione umana. Il build rifiutato non arriva né su TestFlight né in revisione. Un'API privata individuata più tardi da un reviewer ricade invece nella guideline 2.5.1.
Si può correggere ITMS-90338 con un aggiornamento da remoto?
No. I nomi citati sono nel codice nativo compilato del binario. Un aggiornamento da remoto cambia solo il JavaScript e le risorse. Correggi la dipendenza, rifai il build, poi rinvialo.
Perché Apple cita metodi che non ho mai scritto?
Perché arrivano da una libreria inclusa, oppure da una collisione: una dipendenza dichiara un metodo con il nome di un'API privata. Apple legge i nomi presenti nel binario, non il tuo codice sorgente. Risali al pacchetto con grep e npm explain.
Come capire se un'API è pubblica su iOS?
Cerca il suo nome negli header dell'SDK iOS, il cui percorso ti viene dato da xcrun --sdk iphoneos --show-sdk-path, poi leggi la sua disponibilità. Lo indica anche la pagina di documentazione di Apple. Una funzione documentata solo per macOS non è pubblica su iPhone.
Bisogna cambiare il numero di build dopo la correzione?
Sì. Lascia che sia EAS a incrementarlo: un numero già inviato per la stessa versione può essere rifiutato con ITMS-90189.

