ITMS-90338: ret “Non-public API usage” med Expo
Af Mehdi Azizi
Udgivet , 9 min.
ITMS-90338: dit build nævner en metode, funktion eller klasse, som Apple anser for privat. Find den afhængighed, der er skyld i det, opdatér den og byg igen.
ITMS-90338 er en automatisk kontrol, som Apple kører på dit build. Den fortæller, at din binære fil refererer til API'er, som Apple betragter som private: Objective-C-metoder, funktioner eller klasser. Løsningen: find den fil, e-mailen nævner, spor dig frem til den afhængighed, der indeholder navnet, opdatér den eller omdøb din metode, og byg så igen.
Med Expo kommer navnet næsten altid fra et native bibliotek og sjældent fra din JavaScript. Den 15. oktober 2026 kørte jeg analysekommandoerne fra denne guide på et simulator-build af Solys, mit vækkeur med minispil (Expo SDK 57), og på iOS-SDK'et i Xcode 26.2. Her er, hvordan du læser e-mailen, finder synderen og genkender en falsk positiv.
Hvad betyder fejlen ITMS-90338?
Beskeden har titlen "ITMS-90338: Non-public API usage". Den findes i tre varianter, alt efter hvad Apple har fundet i din binære fil:
- "references non-public selectors": navne på Objective-C-metoder, som
boldFontOfSize:; - "references non-public symbols": funktioner, skrevet med en understregning foran, som
__dyld_get_shared_cache_range; - "contains or inherits from non-public classes": et klassenavn.
Hver variant angiver en sti og derefter listen over navne. Teksten giver også to råd: omdøb dine metoder, hvis navnet svarer til en privat API, og fjern det statiske bibliotek, der indeholder dem. En e-mail fra september 2026 foreslår desuden xcrun swift-demangle til at gøre kodede Swift-navne læsbare.
Reglen bag kontrollen er guideline 2.5.1: en app må kun bruge offentlige API'er. Licensaftalen for Apple Developer Program gentager det i punkt 3.3.1: ingen kald til private API'er. Den manuelle gennemgang af denne regel er beskrevet i guiden til guideline 2.5.1. Her handler det om upload-fejlen.
Som med de andre ITMS-fejl har ingen åbnet din app: Apple har analyseret den binære fil under behandlingen af buildet. Apple offentliggør ingen liste over private API'er: navnene i din e-mail er den eneste pålidelige liste.
Læs stien: et SDK eller din eksekverbare fil?
Stien, der står før listen, fortæller dig, hvilken fil du skal lede i.
Frameworks/Nom.framework/Nom: navnet ligger i et indlejret dynamisk framework, altså i et bestemt SDK. I Solys-buildet indeholder mappen 14 frameworks: Hermes, React Native, færdigkompilerede Expo-moduler og billedbiblioteket SDWebImage.- Kun navnet på din app: navnet ligger i den primære eksekverbare fil. Med Expo samler den din native kode og de pods, der er kompileret fra kildekode, som standard linket statisk. Så kan ethvert native bibliotek være skyld i det.
Beslutningstræet: fra det nævnte navn til løsningen
Gå rækkerne igennem i rækkefølge. Den første, der passer på dit tilfælde, giver sporet.
| Det, du ser | Hvor du skal lede | Løsning |
|---|---|---|
Navn med $s eller _$s | xcrun swift-demangle | Start forfra i træet |
| Findes ikke i din IPA | grep på bundlen | Send et nyt build |
| Offentligt navn på iOS | iOS-SDK'ets headers | Send et nyt build |
API_UNAVAILABLE(ios) | iOS-SDK'ets headers | Opdatér pakken |
I Frameworks/X | SDK'et, der leverer X | Opdatér SDK'et |
| Selektor, din app | Pods og native moduler | Tilføj præfiks eller opdatér |
Symbol _nom, din app | nm -m, derefter pods | Erstat kaldet |
| Klasse, din app | Dine klasser og pods | Giv klassen et præfiks |
Rækkerne "Send et nyt build" peger på en falsk positiv, som behandles længere nede.
Trin 1: åbn det uploadede build
Arbejd på den fil, der faktisk blev sendt til Apple, ikke på dit projekt. Download IPA'en fra buildets side på expo.dev, og kør på din Mac:
unzip -q MonApp.ipa -d ipa
ls ipa/Payload/MonApp.app/Frameworks
otool -L ipa/Payload/MonApp.app/MonApp | grep PrivateFrameworks
Den anden linje viser de indlejrede frameworks. Den tredje leder efter et direkte link til et privat Apple-framework: den må ikke vise noget. På Solys finder otool -L intet privat framework, hverken i den eksekverbare fil eller i de 14 frameworks.
Trin 2: find filen, der indeholder navnet
Start bredt. Søg efter det nævnte navn, uden de indledende understregninger, i hele bundlen:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MonApp.app
Kommandoen viser de filer, der indeholder teksten. Gå derefter mere præcist til værks efter navnets type (selektor, symbol, klasse) på den fundne fil, her kaldet Binaire:
strings -a Binaire | grep -x "boldFontOfSize:"
nm -m Binaire | grep "_dyld_get_shared_cache_range"
nm -m Binaire | grep '_OBJC_CLASS_$_NomDeClasse'
Til en selektor skal du bruge strings, ikke nm. På Solys' React-framework viser nm metoder sammen med deres klasse, som -[RCTRootView initWithBridge:moduleName:initialProperties:], men aldrig selektoren initWithBridge: alene. Det gør strings.
For et symbol skal du læse slutningen af linjen. På Solys viser nm -m for eksempel (undefined) external __dyld_get_image_name (from libSystem): den binære fil kalder funktionen uden at indeholde den, og libSystem leverer den. Netop den funktion er offentlig, som næste trin viser.
Trin 3: tjek, om navnet virkelig er privat
iOS-SDK'ets headers beskriver de offentlige API'er. En søgning på få sekunder viser, om det nævnte navn står der, og på hvilke platforme. For et symbol skal du først fjerne den understregning, der tilføjes ved kompileringen: _SCDynamicStoreCreate bliver til SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Den 15. oktober 2026, med iOS 26.2-SDK'et, gav fire navne fra rigtige ITMS-90338-e-mails dette resultat:
| Nævnt navn | I iOS-SDK'et | Tolkning |
|---|---|---|
| _dyld_get_shared_cache_range | Fraværende | Privat API |
| boldFontOfSize: | Fraværende | Privat eller kollision |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Kun macOS |
| isMainFrame | WebKit-property | Offentlig |
Et navn, der findes og er tilgængeligt på iOS, peger på en falsk positiv. Et fraværende navn peger på en privat API eller en kollision. To begrænsninger: en offentlig API, der kun findes i Swift, dukker ikke op i disse headers, og for en accessor som setDelegate: skal du søge efter property'en delegate.
Trin 4: spor dig frem til pakken
Hvis stien nævner et framework, fører navnet dig til poden og derefter til npm-pakken:
grep -n "NomDuFramework" ios/Podfile.lock
npm explain nom-du-paquet
npm explain fortæller, hvilken afhængighed der installerede pakken, hvis du aldrig selv har tilføjet den.
Hvis stien nævner den eksekverbare fil, så søg efter navnet i de native kilder og derefter i pods:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Tilpas listen over mapper: modules er mappen med lokale Expo-moduler. Mappen ios/Pods dukker op efter npx expo prebuild -p ios, som også installerer pods på Mac. Pas på: kommandoen kan genskabe mappen ios. Hvis du har ændret den manuelt, så arbejd på en kopi.
Søgningen i ios/Pods er lige så vigtig som søgningen i kildekoden. På Solys indeholder mappen 15 færdigkompilerede frameworks i .xcframework-format: Expo-moduler, Hermes, React Native og tredjeparts-SDK'er. En søgning, der kun dækker kildefiler, ville overse deres kode.
De typiske årsager
Et tredjeparts-SDK, der er for gammelt eller for nyt. I september 2026 rapporterede en Flutter-udvikler en afvisning på grund af et sikkerheds-SDK: version 8.2.3 kaldte __dyld_get_shared_cache_range. Version 8.2.2 gik igennem, og udgiveren anbefalede at gå tilbage til den, indtil der kom en rettelse.
Et native modul, der kalder en udokumenteret API. Dit eget modul, et plugin eller et lille bibliotek kan bruge en intern UIKit-metode, nogen har fundet på et forum. Kontrollen opdager den på navnet.
En navnekollision. Din metode eller klasse har tilfældigvis samme navn som en privat Apple-API. Beskeden tager højde for det og beder dig omdøbe den. Apple anbefaler at give metoder, du tilføjer til deres klasser, et præfiks, for eksempel tre små bogstaver efterfulgt af en understregning.
En API, der er offentlig på macOS, men ikke på iOS. I 2024 blev en .NET MAUI-app afvist for _SCDynamicStoreCreate. Apple dokumenterer godt nok funktionen, men kun til macOS, og headeren i iOS-SDK'et markerer den API_UNAVAILABLE(ios, tvos, watchos).
Ret fejlen uden at snyde
- Opdatér afhængigheden. For pakker, som Expo følger, viser
npx expo install --checkdem, der ikke passer til dit SDK, ifølge dokumentationen til Expo CLI. For de andre skal du læse changeloggen. - Gå tilbage til den forrige version, hvis det var den nye, der introducerede navnet, og giv udgiveren besked.
- Erstat eller fjern et forladt bibliotek.
- Omdøb din metode eller klasse med et præfiks.
Skjul ikke navnet. At dele en streng op eller kalde metoden ad omveje skjuler brugen uden at rette den. Det er stadig forbudt efter regel 2.5.1 og efter aftalen, og så er det din konto, der er i fare.
Byg derefter igen med et nyt buildnummer. En opdatering over the air er ikke nok: navnet ligger i den kompilerede native kode. Kør trin 2 igen på den nye IPA, før du sender den. Navnet må ikke længere dukke op.
Når det er en falsk positiv
Apples kontrol tager nogle gange fejl. Den 6. juni 2022 fik React Native- og Flutter-apps ITMS-90338 for offentlige WebKit-navne som setNavigationDelegate: eller isMainFrame, også på opdateringer, der næsten ikke var ændret. Dagen efter skrev en Apple-ingeniør, at problemet var løst i valideringen, og opfordrede til at sende buildene igen. Tråden er bevaret i Expos GitHub-repo.
Tre tegn tilsammen peger på en falsk positiv:
- buildet ligner det sidste godkendte build;
- trin 2 finder ikke navnet, eller trin 3 viser, at det er offentligt på iOS;
- andre udviklere melder om samme fejl samme dag på Apples forummer.
I så fald skal du tjekke status for Apples tjenester og derefter sende et nyt build med et nyt nummer. Hvis fejlen fortsætter, så skriv til Apples udviklersupport med hele e-mailen. En navnekollision er derimod ikke en falsk positiv i Apples øjne: omdøb.
Før din næste upload
De andre ITMS-fejl har også deres egen guide: ITMS-90725 om Xcode-versionen, ITMS-91053 om privacy manifest og ITMS-90034 om signaturen. Når buildet er godkendt, så installér det med TestFlight før reviewet.
Afvisninger fra en reviewer kræver mere overblik: fremgangsmåden står på siden om app afvist af Apple, og de hyppigste årsager står i hvorfor Apple afviser en app.
Vil du tjekke dit projekt før upload, samler Review Kit min tjekliste og Claude Code-skills, der gennemgår dit projekt. Hvis din upload sidder fast, og du vil have et menneskeligt blik på det, så book en time med mig: vi gennemgår dit projekt over video, på din skærm. Og forbereder du din første udgivelse, så tag den gratis diagnose: den sætter trinene i den rigtige rækkefølge og sender dig min tjekliste mod afvisninger fra Apple.
Ofte stillede spørgsmål
Er ITMS-90338 en afvisning fra App Review?
Nej. Det er en automatisk kontrol under behandlingen af buildet, før nogen menneskelig gennemgang. Det afviste build når hverken frem til TestFlight eller til review. En privat API, som en reviewer opdager senere, hører derimod under guideline 2.5.1.
Kan man rette ITMS-90338 med en opdatering over the air?
Nej. De nævnte navne ligger i den kompilerede native kode i den binære fil. En opdatering over the air ændrer kun JavaScript og ressourcer. Ret afhængigheden, byg igen, og send buildet på ny.
Hvorfor nævner Apple metoder, jeg aldrig har skrevet?
Fordi de kommer fra et indlejret bibliotek eller fra en kollision: en afhængighed erklærer en metode med samme navn som en privat API. Apple læser navnene i den binære fil, ikke din kildekode. Spor dig frem til pakken med grep og npm explain.
Hvordan ved jeg, om en API er offentlig på iOS?
Søg efter navnet i iOS-SDK'ets headers, hvis sti du får med xcrun --sdk iphoneos --show-sdk-path, og læs dens tilgængelighed. Apples dokumentationsside viser det også. En funktion, der kun er dokumenteret til macOS, er ikke offentlig på iPhone.
Skal jeg ændre buildnummeret efter rettelsen?
Ja. Lad EAS tælle det op: et nummer, der allerede er sendt for samme version, kan blive afvist med ITMS-90189.

