ITMS-90338 beheben: „Non-public API usage“ mit Expo
Von Mehdi Azizi
Veröffentlicht am , 9 Min.
ITMS-90338: Dein Build nutzt eine laut Apple private API. Finde die Abhängigkeit, aktualisiere sie und erstelle einen neuen Build.
ITMS-90338 ist eine automatische Prüfung, die Apple auf deinen Build anwendet. Sie besagt, dass dein Binary auf APIs verweist, die Apple als privat einstuft: Objective-C-Methoden, Funktionen oder Klassen. Die Lösung: Finde die in der E-Mail genannte Datei, verfolge den Namen bis zur Abhängigkeit, die ihn enthält, aktualisiere sie oder benenne deine Methode um und erstelle dann einen neuen Build.
Mit Expo stammt der Name fast immer aus einer nativen Bibliothek, selten aus deinem JavaScript. Am 15. Oktober 2026 habe ich die Analysebefehle aus diesem Guide auf einen Simulator-Build von Solys laufen lassen, meinem Wecker mit Minispielen (Expo SDK 57), und auf das iOS SDK von Xcode 26.2. So liest du die E-Mail, findest den Verursacher und erkennst einen Fehlalarm.
Was bedeutet der Fehler ITMS-90338?
Die Meldung heißt „ITMS-90338: Non-public API usage“. Es gibt sie in drei Varianten, je nachdem, was Apple in deinem Binary gefunden hat:
- „references non-public selectors“: Namen von Objective-C-Methoden, zum Beispiel
boldFontOfSize:; - „references non-public symbols“: Funktionen, mit vorangestelltem Unterstrich geschrieben, zum Beispiel
__dyld_get_shared_cache_range; - „contains or inherits from non-public classes“: ein Klassenname.
Jede Variante nennt einen Pfad und dann die Liste der Namen. Der Text gibt zwei Ratschläge: deine Methoden umzubenennen, falls ihr Name einer privaten API entspricht, und die statische Bibliothek zu entfernen, die sie enthält. Eine E-Mail vom September 2026 schlägt außerdem xcrun swift-demangle vor, um kodierte Swift-Namen lesbar zu machen.
Die Regel hinter dieser Prüfung ist die Guideline 2.5.1: Eine App darf nur öffentliche APIs verwenden. Der Lizenzvertrag des Apple Developer Program wiederholt das in Abschnitt 3.3.1: keine Aufrufe privater APIs. Die menschliche Prüfung dieser Regel erkläre ich im Guide zur Guideline 2.5.1. Hier geht es um den Fehler beim Upload.
Wie bei den anderen ITMS-Fehlern hat niemand deine App geöffnet: Apple hat das Binary während der Verarbeitung des Builds analysiert. Apple veröffentlicht keine Liste privater APIs: Die Namen in deiner E-Mail sind die einzige verlässliche Liste.
Den Pfad lesen: ein SDK oder deine Executable?
Der Pfad vor der Liste sagt dir, in welcher Datei du suchen musst.
Frameworks/Name.framework/Name: Der Name steckt in einem eingebetteten dynamischen Framework, also in einem bestimmten SDK. Im Build von Solys enthält dieser Ordner 14 Frameworks: Hermes, React Native, vorkompiliert ausgelieferte Expo-Module und die Bildbibliothek SDWebImage.- Nur der Name deiner App: Der Name steckt in der Haupt-Executable. Bei Expo vereint sie deinen nativen Code und die aus dem Quellcode kompilierten Pods, die standardmäßig statisch gelinkt sind. Dann kann jede native Bibliothek die Ursache sein.
Der Entscheidungsbaum: vom genannten Namen zur Lösung
Geh die Zeilen der Reihe nach durch. Die erste, die auf deinen Fall passt, gibt dir die Spur.
| Was du liest | Wo du suchst | Lösung |
|---|---|---|
Name mit $s oder _$s | xcrun swift-demangle | Baum erneut durchgehen |
| Nicht in deiner IPA | grep auf das Bundle | Build erneut senden |
| Öffentlicher Name unter iOS | Header des iOS SDK | Build erneut senden |
API_UNAVAILABLE(ios) | Header des iOS SDK | Paket aktualisieren |
In Frameworks/X | Das SDK, das X liefert | SDK aktualisieren |
| Selektor, deine App | Pods und native Module | Präfix setzen oder updaten |
Symbol _name, deine App | nm -m, dann die Pods | Aufruf ersetzen |
| Klasse, deine App | Deine Klassen und deine Pods | Klasse mit Präfix versehen |
Die Zeilen „Build erneut senden“ deuten auf einen Fehlalarm hin, dazu weiter unten mehr.
Schritt 1: den gesendeten Build öffnen
Arbeite mit der Datei, die tatsächlich an Apple ging, nicht mit deinem Projekt. Lade die IPA von der Build-Seite auf expo.dev herunter und führe dann auf deinem Mac aus:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
Die zweite Zeile listet die eingebetteten Frameworks. Die dritte sucht nach einem direkten Link auf ein privates Apple-Framework: Sie darf nichts ausgeben. Bei Solys findet otool -L kein privates Framework, weder in der Executable noch in den 14 Frameworks.
Schritt 2: die Datei finden, die den Namen enthält
Fang breit an. Such den genannten Namen ohne die führenden Unterstriche im gesamten Bundle:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
Der Befehl listet die Dateien, die diesen Text enthalten. Grenz dann je nach Art des Namens (Selektor, Symbol, Klasse) in der gefundenen Datei ein, hier Binary genannt:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Für einen Selektor nimmst du strings, nicht nm. Im React-Framework von Solys zeigt nm Methoden zusammen mit ihrer Klasse an, etwa -[RCTRootView initWithBridge:moduleName:initialProperties:], aber nie den Selektor initWithBridge: allein. strings findet ihn.
Bei einem Symbol liest du das Ende der Zeile. Bei Solys zeigt nm -m zum Beispiel (undefined) external __dyld_get_image_name (from libSystem): Das Binary ruft die Funktion auf, ohne sie zu enthalten, und libSystem stellt sie bereit. Genau diese Funktion ist öffentlich, wie der nächste Schritt zeigt.
Schritt 3: prüfen, ob der Name wirklich privat ist
Die Header des iOS SDK beschreiben die öffentlichen APIs. Eine Suche von ein paar Sekunden zeigt, ob der genannte Name darin vorkommt und auf welchen Plattformen. Bei einem Symbol entfernst du zuerst den Unterstrich, den der Compiler hinzufügt: Aus _SCDynamicStoreCreate wird SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Am 15. Oktober 2026 ergab das mit dem iOS SDK 26.2 für vier Namen aus echten ITMS-90338-E-Mails Folgendes:
| Genannter Name | Im iOS SDK | Bedeutung |
|---|---|---|
| _dyld_get_shared_cache_range | Fehlt | Private API |
| boldFontOfSize: | Fehlt | Privat oder Kollision |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Nur macOS |
| isMainFrame | Property von WebKit | Öffentlich |
Ein vorhandener und unter iOS verfügbarer Name deutet auf einen Fehlalarm hin. Ein fehlender Name deutet auf eine private API oder eine Kollision hin. Zwei Einschränkungen: Eine öffentliche API, die es nur in Swift gibt, taucht in diesen Headern nicht auf, und bei einem Accessor wie setDelegate: suchst du nach der Property delegate.
Schritt 4: bis zum Paket zurückverfolgen
Nennt der Pfad ein Framework, führt dich sein Name zum Pod und dann zum npm-Paket:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain zeigt dir, welche Abhängigkeit das Paket installiert hat, falls du es nie selbst hinzugefügt hast.
Nennt der Pfad die Executable, suchst du den Namen in den nativen Quellen und dann in den Pods:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Pass die Liste der Ordner an: modules ist der Ordner der lokalen Expo-Module. Der Ordner ios/Pods entsteht nach npx expo prebuild -p ios, das auf dem Mac auch die Pods installiert. Achtung: Der Befehl kann den Ordner ios neu erzeugen. Wenn du ihn von Hand geändert hast, arbeite mit einer Kopie.
Die Suche in ios/Pods ist genauso wichtig wie die in den Quellen. Bei Solys enthält dieser Ordner 15 vorkompiliert ausgelieferte Frameworks im Format .xcframework: Expo-Module, Hermes, React Native und Drittanbieter-SDKs. Eine Suche nur in Quelldateien würde deren Code übersehen.
Die typischen Ursachen
Ein Drittanbieter-SDK, zu alt oder zu neu. Im September 2026 hat ein Flutter-Entwickler eine Ablehnung wegen eines Security-SDKs gemeldet: Dessen Version 8.2.3 rief __dyld_get_shared_cache_range auf. Version 8.2.2 ging durch, und der Hersteller riet, bis zu einem Fix darauf zurückzugehen.
Ein natives Modul, das eine undokumentierte API aufruft. Dein eigenes Modul, ein Plugin oder eine kleine Bibliothek kann eine interne UIKit-Methode nutzen, die jemand in einem Forum gefunden hat. Die Prüfung erkennt sie an ihrem Namen.
Eine Namenskollision. Deine Methode oder Klasse trägt zufällig den Namen einer privaten Apple-API. Die Meldung sieht das vor und bittet dich, sie umzubenennen. Apple empfiehlt, Methoden, die du seinen Klassen hinzufügst, mit einem Präfix zu versehen, zum Beispiel mit drei Kleinbuchstaben und einem Unterstrich.
Eine API, die unter macOS öffentlich ist, aber nicht unter iOS. 2024 wurde eine .NET-MAUI-App wegen _SCDynamicStoreCreate abgelehnt. Apple dokumentiert diese Funktion zwar, aber nur für macOS, und der Header des iOS SDK markiert sie mit API_UNAVAILABLE(ios, tvos, watchos).
Beheben statt umgehen
- Aktualisiere die Abhängigkeit. Für Pakete, die Expo verwaltet, meldet
npx expo install --checklaut Dokumentation der Expo CLI diejenigen, die nicht zu deinem SDK passen. Bei den anderen liest du das Changelog. - Geh auf die vorherige Version zurück, wenn die neue den Namen eingeführt hat, und informiere den Hersteller.
- Ersetze oder entferne eine aufgegebene Bibliothek.
- Benenne deine Methode oder Klasse mit einem Präfix um.
Versteck den Namen nicht. Einen String zu zerlegen oder die Methode über einen Umweg aufzurufen verschleiert die Nutzung, ohne sie zu beheben. Sie bleibt nach Regel 2.5.1 und laut Vertrag verboten, und dann steht dein Account auf dem Spiel.
Erstell danach einen neuen Build mit einer neuen Build-Nummer. Ein Over-the-Air-Update reicht nicht: Der Name steckt im kompilierten nativen Code. Wiederhole vor dem Upload Schritt 2 mit der neuen IPA. Der Name darf nicht mehr auftauchen.
Wenn es ein Fehlalarm ist
Apples Prüfung irrt sich manchmal. Am 6. Juni 2022 bekamen React-Native- und Flutter-Apps ITMS-90338 für öffentliche WebKit-Namen wie setNavigationDelegate: oder isMainFrame, sogar bei fast unveränderten Updates. Am nächsten Tag schrieb ein Apple-Ingenieur, das Problem sei auf Validierungsseite behoben, und bat darum, die Builds erneut zu senden. Der Thread ist im GitHub-Repository von Expo erhalten.
Drei Hinweise zusammen deuten auf einen Fehlalarm hin:
- Der Build ähnelt dem letzten akzeptierten Build;
- Schritt 2 findet den Namen nicht, oder Schritt 3 zeigt, dass er unter iOS öffentlich ist;
- andere Entwickler melden am selben Tag denselben Fehler in Apples Foren.
Schau dann auf den Status der Apple-Dienste und sende einen Build mit neuer Nummer. Bleibt der Fehler, schreib dem Apple-Entwickler-Support mit der vollständigen E-Mail. Eine Namenskollision ist in Apples Augen dagegen kein Fehlalarm: Benenne um.
Vor deinem nächsten Upload
Die anderen ITMS-Fehler haben auch ihren Guide: ITMS-90725 für die Xcode-Version, ITMS-91053 für das Privacy Manifest und ITMS-90034 für die Signatur. Sobald der Build akzeptiert ist, installier ihn vor dem Review über TestFlight.
Ablehnungen durch einen Reviewer brauchen mehr Abstand: Das Vorgehen steht auf der Seite App von Apple abgelehnt, und die häufigsten Gründe findest du in warum Apple eine App ablehnt.
Um deine Unterlagen vor dem Upload zu prüfen, vereint das Review Kit meine Checkliste und Claude-Code-Skills, die dein Projekt durchgehen. Wenn dein Upload hängt und du einen menschlichen Blick willst, buch eine Stunde mit mir: Wir gehen dein Projekt per Video an deinem Bildschirm durch. Und wenn du deinen ersten Release vorbereitest, mach deine kostenlose Diagnose: Sie bringt die Schritte in die richtige Reihenfolge und schickt dir meine Anti-Ablehnungs-Checkliste für Apple.
Häufige Fragen
Ist ITMS-90338 eine Ablehnung durch das App Review?
Nein. Es ist eine automatische Prüfung während der Verarbeitung des Builds, vor jedem menschlichen Review. Der abgelehnte Build landet weder in TestFlight noch im Review. Eine private API, die später ein Reviewer entdeckt, fällt dagegen unter Guideline 2.5.1.
Kann man ITMS-90338 mit einem Over-the-Air-Update beheben?
Nein. Die genannten Namen stecken im kompilierten nativen Code des Binarys. Ein Over-the-Air-Update ändert nur das JavaScript und die Ressourcen. Korrigiere die Abhängigkeit, erstell einen neuen Build und sende ihn erneut.
Warum nennt Apple Methoden, die ich nie geschrieben habe?
Weil sie aus einer eingebetteten Bibliothek stammen oder aus einer Kollision: Eine Abhängigkeit deklariert eine Methode, die den Namen einer privaten API trägt. Apple liest die Namen im Binary, nicht deinen Quellcode. Verfolge sie mit grep und npm explain bis zum Paket zurück.
Wie finde ich heraus, ob eine API unter iOS öffentlich ist?
Such ihren Namen in den Headern des iOS SDK, deren Pfad dir xcrun --sdk iphoneos --show-sdk-path liefert, und lies ihre Verfügbarkeit. Auch Apples Dokumentationsseite gibt sie an. Eine Funktion, die nur für macOS dokumentiert ist, ist auf dem iPhone nicht öffentlich.
Muss ich nach der Korrektur die Build-Nummer ändern?
Ja. Lass EAS sie hochzählen: Eine für dieselbe Version bereits gesendete Nummer kann mit ITMS-90189 abgelehnt werden.

