# ITMS-90338 oplossen: “Non-public API usage” met Expo

> ITMS-90338: je build noemt een methode, functie of klasse die Apple als privé ziet. Zoek de dependency, update die en maak een nieuwe build.

Originele pagina: [ITMS-90338 oplossen: “Non-public API usage” met Expo](https://devoria.fr/nl/blog/itms-90338-non-public-api-usage-expo)
Auteur: [Mehdi Azizi](https://devoria.fr/nl/mehdi-azizi)
Publicatie: 2026-11-04
Laatste inhoudsrevisie: 2026-11-04


ITMS-90338 is een automatische controle van Apple op je build. Die zegt dat je binary verwijst naar API's die Apple als privé beschouwt: Objective-C-methodes, functies of klassen. **De oplossing: zoek het bestand dat in de e-mail genoemd wordt, spoor de dependency op die de naam bevat, update die of hernoem je methode, en maak daarna een nieuwe build.**

Met Expo komt de naam bijna altijd uit een native library, zelden uit je JavaScript. Op 15 oktober 2026 heb ik de analysecommando's uit deze gids gedraaid op een simulatorbuild van Solys, mijn wekker met minigames (Expo SDK 57), en op de iOS SDK van Xcode 26.2. Zo lees je de e-mail, vind je de boosdoener en herken je een false positive.

## Wat betekent de fout ITMS-90338?

Het bericht heet "ITMS-90338: Non-public API usage". Er zijn drie varianten, afhankelijk van wat Apple in je binary heeft gevonden:

- **"references non-public selectors"**: namen van Objective-C-methodes, zoals `boldFontOfSize:`;
- **"references non-public symbols"**: functies, geschreven met een underscore ervoor, zoals `__dyld_get_shared_cache_range`;
- **"contains or inherits from non-public classes"**: een klassenaam.

Elke variant geeft een pad en daarna de lijst met namen. De tekst geeft twee adviezen: hernoem je methodes als hun naam overeenkomt met een privé-API, en verwijder de statische library die ze bevat. Een e-mail van september 2026 stelt ook `xcrun swift-demangle` voor om gecodeerde Swift-namen leesbaar te maken.

De regel achter deze controle is [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): een app mag alleen publieke API's gebruiken. De [licentieovereenkomst van het Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) herhaalt dat in artikel 3.3.1: geen enkele aanroep van een privé-API. De menselijke review van deze regel staat uitgelegd in de gids over [guideline 2.5.1](https://devoria.fr/nl/blog/guideline-2-5-1-publieke-api-sdk-expo). Hier behandelen we de uploadfout.

Net als bij de andere ITMS-fouten heeft niemand je app geopend: Apple heeft de binary geanalyseerd tijdens het verwerken van de build. **Apple publiceert geen lijst met privé-API's: de namen in je e-mail zijn de enige betrouwbare lijst.**

## Het pad lezen: een SDK of je executable?

Het pad vóór de lijst vertelt je in welk bestand je moet zoeken.

- **`Frameworks/Nom.framework/Nom`**: de naam zit in een ingebed dynamisch framework, dus in een specifieke SDK. In de build van Solys bevat deze map 14 frameworks: Hermes, React Native, voorgecompileerde Expo-modules en de afbeeldingslibrary SDWebImage.
- **Alleen de naam van je app**: de naam zit in de hoofd-executable. Met Expo bevat die je native code en de pods die vanuit hun broncode gecompileerd zijn, standaard statisch gelinkt. Dan kan elke native library de oorzaak zijn.

## De beslisboom: van genoemde naam naar oplossing

Loop de regels op volgorde door. De eerste die bij jouw situatie past, geeft het spoor.

| Wat je leest             | Waar je zoekt           | Oplossing                   |
| ------------------------ | ----------------------- | --------------------------- |
| Naam met `$s` of `_$s`   | `xcrun swift-demangle`  | Boom opnieuw doorlopen      |
| Niet te vinden in je IPA | `grep` op de bundle     | Build opnieuw insturen      |
| Publieke naam op iOS     | Headers van de iOS SDK  | Build opnieuw insturen      |
| `API_UNAVAILABLE(ios)`   | Headers van de iOS SDK  | Package updaten             |
| In `Frameworks/X`        | De SDK die X levert     | SDK updaten                 |
| Selector, je app         | Pods en native modules  | Prefix toevoegen of updaten |
| Symbool `_nom`, je app   | `nm -m`, daarna de pods | Aanroep vervangen           |
| Klasse, je app           | Je klassen en je pods   | Klasse een prefix geven     |

De regels "Build opnieuw insturen" wijzen op een false positive, die verderop aan bod komt.

## Stap 1: de ingestuurde build openen

Werk met het bestand dat echt naar Apple is gestuurd, niet met je project. Download de IPA vanaf de buildpagina op expo.dev en voer dit uit op je Mac:

```bash
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
```

De tweede regel toont de ingebedde frameworks. De derde zoekt naar een directe link naar een privéframework van Apple: die mag niets tonen. Bij Solys vindt `otool -L` geen enkel privéframework, niet in de executable en niet in de 14 frameworks.

## Stap 2: het bestand vinden dat de naam bevat

Begin breed. Zoek de genoemde naam, zonder de underscores aan het begin, in de hele bundle:

```bash
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
```

Het commando toont de bestanden die deze tekst bevatten. Verfijn daarna op basis van het type naam (selector, symbool, klasse), op het gevonden bestand, hier `Binaire` genoemd:

```bash
strings -a Binaire | grep -x "boldFontOfSize:"
nm -m Binaire | grep "_dyld_get_shared_cache_range"
nm -m Binaire | grep '_OBJC_CLASS_$_NomDeClasse'
```

**Gebruik voor een selector `strings`, niet `nm`.** Op het React-framework van Solys toont `nm` methodes samen met hun klasse, zoals `-[RCTRootView initWithBridge:moduleName:initialProperties:]`, maar nooit de losse selector `initWithBridge:`. `strings` vindt die wel.

Lees bij een symbool het einde van de regel. Bij Solys toont `nm -m` bijvoorbeeld `(undefined) external __dyld_get_image_name (from libSystem)`: de binary roept de functie aan zonder haar zelf te bevatten, en libSystem levert haar. Deze functie is publiek, zoals de volgende stap laat zien.

## Stap 3: controleren of de naam echt privé is

De headers van de iOS SDK beschrijven de publieke API's. Met een zoekactie van een paar seconden weet je of de genoemde naam erin staat, en op welke platforms. Haal bij een symbool eerst de underscore weg die bij het compileren is toegevoegd: `_SCDynamicStoreCreate` wordt `SCDynamicStoreCreate`.

```bash
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
```

Op 15 oktober 2026, met iOS SDK 26.2, gaven vier namen uit echte ITMS-90338-e-mails dit resultaat:

| Genoemde naam                 | In de iOS SDK        | Betekenis        |
| ----------------------------- | -------------------- | ---------------- |
| \_dyld_get_shared_cache_range | Afwezig              | Privé-API        |
| boldFontOfSize:               | Afwezig              | Privé of botsing |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios) | Alleen macOS     |
| isMainFrame                   | Property van WebKit  | Publiek          |

**Een naam die aanwezig en beschikbaar is op iOS wijst op een false positive. Een afwezige naam wijst op een privé-API of een naambotsing.** Twee beperkingen: een API die alleen in Swift publiek is, staat niet in deze headers, en zoek bij een accessor als `setDelegate:` naar de property `delegate`.

## Stap 4: terug naar het package

Als het pad een framework noemt, leidt de naam daarvan naar de pod en dan naar het npm-package:

```bash
grep -n "NomDuFramework" ios/Podfile.lock
npm explain nom-du-paquet
```

`npm explain` vertelt welke dependency het heeft geïnstalleerd, als je het zelf nooit hebt toegevoegd.

Als het pad de executable noemt, zoek de naam dan in de native broncode en daarna in de pods:

```bash
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
  "boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
```

Pas de lijst met mappen aan: `modules` is de map met lokale Expo-modules. De map `ios/Pods` verschijnt na `npx expo prebuild -p ios`, dat op een Mac ook de pods installeert. Let op: dit commando kan de map `ios` opnieuw aanmaken. Heb je die met de hand aangepast, werk dan op een kopie.

**De zoekactie in `ios/Pods` telt net zo zwaar als die in de broncode.** Bij Solys bevat deze map 15 voorgecompileerde frameworks in `.xcframework`-formaat: Expo-modules, Hermes, React Native en SDK's van derden. Een zoekactie die zich beperkt tot bronbestanden mist hun code.

## De typische oorzaken

**Een SDK van derden, te oud of te nieuw.** In september 2026 [meldde een Flutter-developer een afwijzing door een security-SDK](https://github.com/talsec/Free-RASP-Flutter/issues/236): versie 8.2.3 riep `__dyld_get_shared_cache_range` aan. Versie 8.2.2 kwam er wel door, en de maker adviseerde terug te gaan naar die versie in afwachting van een fix.

**Een native module die een ongedocumenteerde API aanroept.** Je eigen module, een plugin of een kleine library kan een interne UIKit-methode gebruiken die iemand op een forum vond. De controle herkent die aan de naam.

**Een naambotsing.** Je methode of klasse heeft toevallig de naam van een privé-API van Apple. Het bericht houdt daar rekening mee en vraagt je om haar te hernoemen. Apple raadt aan om [methodes die je aan zijn klassen toevoegt een prefix te geven](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), bijvoorbeeld drie kleine letters gevolgd door een underscore.

**Een API die publiek is op macOS, maar niet op iOS.** In 2024 werd een .NET MAUI-app [afgewezen vanwege `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple documenteert die functie wel, maar [alleen voor macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), en de header van de iOS SDK markeert haar als `API_UNAVAILABLE(ios, tvos, watchos)`.

## Oplossen zonder te omzeilen

1. **Update de dependency.** Voor packages die Expo bijhoudt, meldt `npx expo install --check` welke niet bij je SDK passen, volgens de [documentatie van de Expo CLI](https://docs.expo.dev/more/expo-cli/). Lees voor de andere de changelog.
2. **Ga terug naar de vorige versie** als de nieuwe de naam heeft geïntroduceerd, en laat het de maker weten.
3. **Vervang of verwijder** een library die niet meer onderhouden wordt.
4. **Hernoem** je methode of klasse, met een prefix.

**Verberg de naam niet.** Een string opknippen of de methode via een omweg aanroepen verstopt het gebruik zonder het op te lossen. Het blijft verboden door regel 2.5.1 en door de overeenkomst, en dan loopt je account het risico.

Maak daarna een nieuwe build met een nieuw [buildnummer](https://devoria.fr/nl/blog/buildnummer-app-store-connect-versie). Een update op afstand is niet genoeg: de naam zit in de gecompileerde native code. Voer vóór het insturen stap 2 opnieuw uit op de nieuwe IPA. De naam mag er niet meer in voorkomen.

## Als het een false positive is

De controle van Apple vergist zich soms. Op 6 juni 2022 kregen React Native- en Flutter-apps ITMS-90338 voor publieke WebKit-namen, zoals `setNavigationDelegate:` of `isMainFrame`, zelfs bij updates die bijna niet veranderd waren. De dag erna schreef een engineer van Apple dat het probleem aan de validatiekant was opgelost en vroeg hij om de builds opnieuw in te sturen. De thread staat nog in de [GitHub-repository van Expo](https://github.com/expo/expo/issues/17768).

Drie aanwijzingen samen wijzen op een false positive:

- de build lijkt op de laatste geaccepteerde build;
- stap 2 vindt de naam niet, of stap 3 laat zien dat hij publiek is op iOS;
- andere developers melden dezelfde dag dezelfde fout op de forums van Apple.

Kijk in dat geval naar de [status van de diensten van Apple](https://developer.apple.com/system-status/) en stuur daarna een build in met een nieuw nummer. Blijft de fout, schrijf dan naar de [developersupport van Apple](https://developer.apple.com/contact/) met de volledige e-mail. Een naambotsing is in de ogen van Apple geen false positive: hernoem.

## Vóór je volgende upload

De andere ITMS-fouten hebben ook een eigen gids: [ITMS-90725](https://devoria.fr/nl/blog/itms-90725-sdk-version-issue-expo) voor de Xcode-versie, [ITMS-91053](https://devoria.fr/nl/blog/itms-91053-missing-api-declaration-expo) voor het privacy manifest en [ITMS-90034](https://devoria.fr/nl/blog/itms-90034-missing-invalid-signature-oplossen) voor de signing. Is de build geaccepteerd, installeer hem dan met [TestFlight](https://devoria.fr/nl/blog/testflight-iphone-app-testen-voor-indienen) vóór de review.

Afwijzingen door een reviewer vragen meer afstand: de aanpak staat op de pagina [app afgewezen door Apple](https://devoria.fr/nl/expertise/app-afgewezen-door-apple), en de meest voorkomende redenen vind je in [waarom Apple een app afwijst](https://devoria.fr/nl/blog/app-afgewezen-door-apple-waarom).

Om je dossier vóór het insturen te controleren, bundelt de [Review Kit](https://devoria.fr/nl/kits/app-review) mijn checklist en Claude Code-skills die je project nalopen. Loopt je upload vast en wil je dat een mens meekijkt, [boek dan een uur met mij](https://devoria.fr/nl/coaching): we lopen je project samen door via een videocall, op jouw scherm. En bereid je je eerste release voor, [doe dan je gratis diagnose](https://devoria.fr/nl/diagnostic): die zet de stappen op volgorde en stuurt je mijn checklist tegen afwijzingen door Apple.

## Veelgestelde vragen

### Is ITMS-90338 een afwijzing door App Review?

Nee. Het is een automatische controle tijdens het verwerken van de build, vóór elke menselijke review. De afgewezen build komt niet in TestFlight en niet in review terecht. Een privé-API die later door een reviewer wordt opgemerkt, valt wel onder guideline 2.5.1.

### Kun je ITMS-90338 oplossen met een update op afstand?

Nee. De genoemde namen zitten in de gecompileerde native code van de binary. Een update op afstand verandert alleen het JavaScript en de resources. Los de dependency op, maak een nieuwe build en stuur die opnieuw in.

### Waarom noemt Apple methodes die ik nooit heb geschreven?

Omdat ze uit een ingebedde library komen, of uit een naambotsing: een dependency declareert een methode met de naam van een privé-API. Apple leest de namen in de binary, niet je broncode. Spoor het package op met `grep` en `npm explain`.

### Hoe weet je of een API publiek is op iOS?

Zoek de naam in de headers van de iOS SDK, waarvan `xcrun --sdk iphoneos --show-sdk-path` het pad geeft, en lees de beschikbaarheid. De documentatiepagina van Apple vermeldt die ook. Een functie die alleen voor macOS gedocumenteerd is, is niet publiek op de iPhone.

### Moet je het buildnummer aanpassen na de oplossing?

Ja. Laat EAS het ophogen: een nummer dat al voor dezelfde versie is ingestuurd, kan worden afgewezen met ITMS-90189.


## Je project

[Review Kit](https://devoria.fr/nl/kits/app-review): Mijn App Store-reviewchecklist, mijn opgeloste afwijzingen door Apple, die voor Google Play en drie Claude Code-skills. € 49.

[iOS & Android Kit](https://devoria.fr/nl/kits/ios): Bouw je iPhone- en Android-app met Claude Code: de startcode van mijn apps, de route naar de stores en alle kits. € 99.

[Diagnose: wat staat er tussen jou en je iPhone-app](https://devoria.fr/nl/diagnostic): 7 vragen, 60 seconden: je weet wat je echt tegenhoudt om je iPhone-app te publiceren, je traject in 4 stappen en de kit die bij je past.
