ITMS-90338 : corriger « Non-public API usage » avec Expo
Par Mehdi Azizi
Publié le , 9 min
ITMS-90338 : ton build cite une méthode, une fonction ou une classe qu'Apple juge privée. Trouve la dépendance en cause, mets-la à jour, puis refais un build.
ITMS-90338 est un contrôle automatique d'Apple sur ton build. Il dit que ton binaire fait référence à des API qu'Apple considère comme privées : des méthodes Objective-C, des fonctions ou des classes. La correction : repérer le fichier cité dans l'e-mail, remonter jusqu'à la dépendance qui contient le nom, la mettre à jour ou renommer ta méthode, puis refaire un build.
Avec Expo, le nom vient presque toujours d'une bibliothèque native, rarement de ton JavaScript. Le 15 octobre 2026, j'ai lancé les commandes d'analyse de ce guide sur un build simulateur de Solys, mon réveil à mini-jeux (SDK Expo 57), et sur le SDK iOS de Xcode 26.2. Voici comment lire l'e-mail, trouver le coupable et reconnaître un faux positif.
Que veut dire l'erreur ITMS-90338 ?
Le message s'intitule « ITMS-90338: Non-public API usage ». Il existe en trois variantes, selon ce qu'Apple a trouvé dans ton binaire :
- « references non-public selectors » : des noms de méthodes Objective-C, comme
boldFontOfSize:; - « references non-public symbols » : des fonctions, écrites avec un tiret bas devant, comme
__dyld_get_shared_cache_range; - « contains or inherits from non-public classes » : un nom de classe.
Chaque variante donne un chemin, puis la liste des noms. Le texte ajoute deux conseils : renommer tes méthodes si leur nom correspond à une API privée, et retirer la bibliothèque statique qui les contient. Un e-mail de septembre 2026 propose aussi xcrun swift-demangle pour rendre lisibles les noms Swift encodés.
La règle derrière ce contrôle est la guideline 2.5.1 : une app ne peut utiliser que des API publiques. Le contrat de licence de l'Apple Developer Program le redit à son article 3.3.1 : aucun appel à une API privée. La revue humaine de cette règle est détaillée dans le guide de la guideline 2.5.1. Ici, on traite l'erreur d'envoi.
Comme les autres erreurs ITMS, personne n'a ouvert ton app : Apple a analysé le binaire pendant le traitement du build. Apple ne publie aucune liste des API privées : les noms de ton e-mail sont la seule liste fiable.
Lire le chemin : un SDK ou ton exécutable ?
Le chemin écrit avant la liste te dit dans quel fichier chercher.
Frameworks/Nom.framework/Nom: le nom se trouve dans un framework dynamique embarqué, donc dans un SDK précis. Sur le build de Solys, ce dossier contient 14 frameworks : Hermes, React Native, des modules Expo livrés déjà compilés et la bibliothèque d'images SDWebImage.- Le nom de ton app seul : le nom se trouve dans l'exécutable principal. Avec Expo, il réunit ton code natif et les pods compilés depuis leurs sources, liés en statique par défaut. N'importe quelle bibliothèque native peut alors être en cause.
L'arbre : du nom cité à la correction
Prends les lignes dans l'ordre. La première qui correspond à ton cas donne la piste.
| Ce que tu lis | Où chercher | Correction |
|---|---|---|
Nom en $s ou _$s | xcrun swift-demangle | Reprendre l'arbre |
| Introuvable dans ton IPA | grep sur le bundle | Renvoyer un build |
| Nom public sur iOS | En-têtes du SDK iOS | Renvoyer un build |
API_UNAVAILABLE(ios) | En-têtes du SDK iOS | Mettre à jour le paquet |
Dans Frameworks/X | Le SDK qui fournit X | Mettre à jour le SDK |
| Sélecteur, ton app | Pods et modules natifs | Préfixer ou mettre à jour |
Symbole _nom, ton app | nm -m, puis les pods | Remplacer l'appel |
| Classe, ton app | Tes classes et tes pods | Préfixer la classe |
Les lignes « Renvoyer un build » signalent un faux positif, traité plus bas.
Étape 1 : ouvrir le build envoyé
Travaille sur le fichier réellement envoyé à Apple, pas sur ton projet. Télécharge l'IPA depuis la page du build sur expo.dev, puis lance sur ton Mac :
unzip -q MonApp.ipa -d ipa
ls ipa/Payload/MonApp.app/Frameworks
otool -L ipa/Payload/MonApp.app/MonApp | grep PrivateFrameworks
La deuxième ligne liste les frameworks embarqués. La troisième cherche un lien direct vers un framework privé d'Apple : elle ne doit rien afficher. Sur Solys, otool -L ne trouve aucun framework privé, ni dans l'exécutable ni dans les 14 frameworks.
Étape 2 : trouver le fichier qui contient le nom
Commence large. Cherche le nom cité, sans les tirets bas du début, dans tout le bundle :
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MonApp.app
La commande liste les fichiers qui contiennent ce texte. Précise ensuite selon le type de nom (sélecteur, symbole, classe), sur le fichier trouvé, noté Binaire :
strings -a Binaire | grep -x "boldFontOfSize:"
nm -m Binaire | grep "_dyld_get_shared_cache_range"
nm -m Binaire | grep '_OBJC_CLASS_$_NomDeClasse'
Pour un sélecteur, utilise strings, pas nm. Sur le framework React de Solys, nm affiche des méthodes avec leur classe, comme -[RCTRootView initWithBridge:moduleName:initialProperties:], jamais le sélecteur initWithBridge: seul. strings le trouve.
Pour un symbole, lis la fin de la ligne. Sur Solys, nm -m affiche par exemple (undefined) external __dyld_get_image_name (from libSystem) : le binaire appelle la fonction sans la contenir, et libSystem la fournit. Cette fonction-là est publique, comme le montre l'étape suivante.
Étape 3 : vérifier si le nom est vraiment privé
Les en-têtes du SDK iOS décrivent les API publiques. Une recherche de quelques secondes dit si le nom cité y figure, et sur quelles plateformes. Pour un symbole, retire d'abord le tiret bas ajouté à la compilation : _SCDynamicStoreCreate devient SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Le 15 octobre 2026, avec le SDK iOS 26.2, voici ce que donnent quatre noms tirés de vrais e-mails ITMS-90338 :
| Nom cité | Dans le SDK iOS | Lecture |
|---|---|---|
| _dyld_get_shared_cache_range | Absent | API privée |
| boldFontOfSize : | Absent | Privée ou collision |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | macOS seulement |
| isMainFrame | Propriété de WebKit | Publique |
Un nom présent et disponible sur iOS pointe vers un faux positif. Un nom absent pointe vers une API privée ou une collision. Deux limites : une API publique en Swift seulement n'apparaît pas dans ces en-têtes, et pour un accesseur comme setDelegate:, cherche la propriété delegate.
Étape 4 : remonter jusqu'au paquet
Si le chemin cite un framework, son nom mène au pod, puis au paquet npm :
grep -n "NomDuFramework" ios/Podfile.lock
npm explain nom-du-paquet
npm explain dit quelle dépendance l'a installé, si tu ne l'as jamais ajouté toi‑même.
Si le chemin cite l'exécutable, cherche le nom dans les sources natives, puis dans les pods :
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Adapte la liste des dossiers : modules est celui des modules Expo locaux. Le dossier ios/Pods apparaît après npx expo prebuild -p ios, qui installe aussi les pods sur Mac. Attention : elle peut recréer le dossier ios. Si tu l'as modifié à la main, travaille sur une copie.
La recherche dans ios/Pods compte autant que celle des sources. Sur Solys, ce dossier contient 15 frameworks livrés déjà compilés, au format .xcframework : modules Expo, Hermes, React Native et SDK tiers. Une recherche limitée aux fichiers source passerait à côté de leur code.
Les causes typiques
Un SDK tiers, trop ancien ou trop récent. En septembre 2026, un développeur Flutter a signalé un refus dû à un SDK de sécurité : sa version 8.2.3 appelait __dyld_get_shared_cache_range. La version 8.2.2 passait, et l'éditeur a conseillé d'y revenir en attendant un correctif.
Un module natif qui appelle une API non documentée. Ton propre module, un plugin ou une petite bibliothèque peut utiliser une méthode interne d'UIKit trouvée sur un forum. Le contrôle la repère par son nom.
Une collision de nom. Ta méthode ou ta classe porte, par hasard, le nom d'une API privée d'Apple. Le message le prévoit et te demande de la renommer. Apple recommande de préfixer les méthodes ajoutées à ses classes, par exemple avec trois lettres en minuscules suivies d'un tiret bas.
Une API publique sur macOS, pas sur iOS. En 2024, une app .NET MAUI a été refusée pour _SCDynamicStoreCreate. Apple documente bien cette fonction, mais pour macOS seulement, et l'en-tête du SDK iOS la marque API_UNAVAILABLE(ios, tvos, watchos).
Corriger sans contourner
- Mets à jour la dépendance. Pour les paquets suivis par Expo,
npx expo install --checksignale ceux qui ne correspondent pas à ton SDK, d'après la documentation de l'Expo CLI. Pour les autres, lis le journal des versions. - Reviens à la version précédente si c'est la nouvelle qui a introduit le nom, et préviens l'éditeur.
- Remplace ou retire une bibliothèque abandonnée.
- Renomme ta méthode ou ta classe, avec un préfixe.
Ne masque pas le nom. Découper une chaîne ou appeler la méthode par un détour cache l'usage sans le corriger. Il reste interdit par la règle 2.5.1 et par le contrat, et c'est alors ton compte qui est exposé.
Ensuite, refais un build avec un nouveau numéro de build. Une mise à jour à distance ne suffit pas : le nom est dans le code natif compilé. Avant l'envoi, relance l'étape 2 sur la nouvelle IPA. Le nom ne doit plus apparaître.
Quand c'est un faux positif
Le contrôle d'Apple se trompe parfois. Le 6 juin 2022, des apps React Native et Flutter ont reçu ITMS-90338 pour des noms publics de WebKit, comme setNavigationDelegate: ou isMainFrame, y compris sur des mises à jour presque inchangées. Le lendemain, un ingénieur d'Apple a écrit que le problème était résolu côté validation et a invité à renvoyer les builds. Le fil est conservé dans le dépôt GitHub d'Expo.
Trois indices réunis pointent vers un faux positif :
- le build ressemble au dernier build accepté ;
- l'étape 2 ne trouve pas le nom, ou l'étape 3 le montre public sur iOS ;
- d'autres développeurs signalent la même erreur le même jour sur les forums d'Apple.
Dans ce cas, regarde l'état des services d'Apple, puis renvoie un build avec un nouveau numéro. Si l'erreur persiste, écris au support développeur d'Apple avec l'e-mail complet. Une collision de nom, elle, n'est pas un faux positif aux yeux d'Apple : renomme.
Avant ton prochain envoi
Les autres erreurs ITMS ont aussi leur guide : ITMS-90725 pour la version de Xcode, ITMS-91053 pour le manifeste de confidentialité et ITMS-90034 pour la signature. Une fois le build accepté, installe-le avec TestFlight avant la revue.
Les refus d'un reviewer demandent plus de recul : la démarche est sur la page application refusée par Apple, et les motifs les plus fréquents sont dans pourquoi Apple refuse une app.
Pour vérifier ton dossier avant l'envoi, le Kit Review Apple réunit ma checklist et des skills Claude Code qui relisent ton projet. Si ton envoi bloque et que tu veux un regard humain, réserve une heure avec moi : on reprend ton projet en visio, sur ton écran. Et si tu prépares ta première sortie, fais ton diagnostic gratuit : il remet les étapes dans l'ordre et t'envoie ma checklist anti-refus Apple.
Questions fréquentes
ITMS-90338 est-il un refus de l'App Review ?
Non. C'est un contrôle automatique fait pendant le traitement du build, avant toute revue humaine. Le build refusé n'arrive ni dans TestFlight ni en revue. Une API privée repérée plus tard par un reviewer relève, elle, de la guideline 2.5.1.
Peut-on corriger ITMS-90338 avec une mise à jour à distance ?
Non. Les noms cités sont dans le code natif compilé du binaire. Une mise à jour à distance ne change que le JavaScript et les ressources. Corrige la dépendance, refais un build, puis renvoie-le.
Pourquoi Apple cite des méthodes que je n'ai jamais écrites ?
Parce qu'elles viennent d'une bibliothèque embarquée, ou d'une collision : une dépendance déclare une méthode qui porte le nom d'une API privée. Apple lit les noms présents dans le binaire, pas ton code source. Remonte jusqu'au paquet avec grep et npm explain.
Comment savoir si une API est publique sur iOS ?
Cherche son nom dans les en-têtes du SDK iOS, dont xcrun --sdk iphoneos --show-sdk-path donne le chemin, puis lis sa disponibilité. La page de documentation d'Apple l'indique aussi. Une fonction documentée pour macOS seulement n'est pas publique sur iPhone.
Faut-il changer le numéro de build après la correction ?
Oui. Laisse EAS l'incrémenter : un numéro déjà envoyé pour la même version peut être refusé en ITMS-90189.

