# ITMS-90338: rezolvă „Non-public API usage” cu Expo

> ITMS-90338: build-ul tău conține o metodă, o funcție sau o clasă pe care Apple o consideră privată. Găsește dependența, actualizeaz-o și refă build-ul.

Pagina originală: [ITMS-90338: rezolvă „Non-public API usage” cu Expo](https://devoria.fr/ro/blog/itms-90338-non-public-api-usage-expo)
Autor: [Mehdi Azizi](https://devoria.fr/ro/mehdi-azizi)
Publicare: 2026-11-04
Ultima revizuire a conținutului: 2026-11-04


ITMS-90338 este o verificare automată pe care Apple o face pe build-ul tău. Îți spune că binarul tău face referire la API-uri pe care Apple le consideră private: metode Objective-C, funcții sau clase. **Soluția: identifici fișierul menționat în e-mail, urmărești numele până la dependența care îl conține, o actualizezi sau îți redenumești metoda, apoi refaci build-ul.**

Cu Expo, numele vine aproape mereu dintr-o bibliotecă nativă, rar din JavaScript-ul tău. Pe 15 octombrie 2026, am rulat comenzile de analiză din acest ghid pe un build de simulator al Solys, alarma mea cu mini-jocuri (Expo SDK 57), și pe SDK-ul iOS din Xcode 26.2. Uite cum citești e-mailul, cum găsești vinovatul și cum recunoști un fals pozitiv.

## Ce înseamnă eroarea ITMS-90338?

Mesajul are titlul „ITMS-90338: Non-public API usage”. Există în trei variante, în funcție de ce a găsit Apple în binarul tău:

- **„references non-public selectors”**: nume de metode Objective-C, cum ar fi `boldFontOfSize:`;
- **„references non-public symbols”**: funcții, scrise cu underscore în față, cum ar fi `__dyld_get_shared_cache_range`;
- **„contains or inherits from non-public classes”**: un nume de clasă.

Fiecare variantă dă o cale, apoi lista de nume. Textul adaugă două sfaturi: să-ți redenumești metodele dacă numele lor coincide cu un API privat și să scoți biblioteca statică ce le conține. Un e-mail din septembrie 2026 propune și `xcrun swift-demangle` ca să faci lizibile numele Swift codificate.

Regula din spatele acestei verificări este [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): o aplicație poate folosi doar API-uri publice. [Contractul de licență al Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) repetă asta la articolul 3.3.1: niciun apel la un API privat. Review-ul uman al acestei reguli e detaliat în ghidul despre [guideline 2.5.1](https://devoria.fr/ro/blog/guideline-2-5-1-api-publice-sdk-expo). Aici ne ocupăm de eroarea de upload.

Ca la celelalte erori ITMS, nimeni nu ți-a deschis aplicația: Apple a analizat binarul în timpul procesării build-ului. **Apple nu publică nicio listă cu API-urile private: numele din e-mailul tău sunt singura listă de încredere.**

## Citește calea: un SDK sau executabilul tău?

Calea scrisă înaintea listei îți spune în ce fișier să cauți.

- **`Frameworks/Nom.framework/Nom`**: numele se află într-un framework dinamic inclus, deci într-un SDK anume. Pe build-ul Solys, acest folder conține 14 framework-uri: Hermes, React Native, module Expo livrate deja compilate și biblioteca de imagini SDWebImage.
- **Doar numele aplicației tale**: numele se află în executabilul principal. Cu Expo, acesta reunește codul tău nativ și pod-urile compilate din surse, legate static în mod implicit. Atunci orice bibliotecă nativă poate fi de vină.

## Arborele: de la numele citat la soluție

Ia rândurile în ordine. Primul care se potrivește cu situația ta îți dă pista.

| Ce citești                   | Unde cauți                | Soluție                     |
| ---------------------------- | ------------------------- | --------------------------- |
| Nume cu `$s` sau `_$s`       | `xcrun swift-demangle`    | Reia arborele               |
| Nu apare în IPA-ul tău       | `grep` pe bundle          | Retrimite un build          |
| Nume public pe iOS           | Header-ele SDK-ului iOS   | Retrimite un build          |
| `API_UNAVAILABLE(ios)`       | Header-ele SDK-ului iOS   | Actualizează pachetul       |
| În `Frameworks/X`            | SDK-ul care furnizează X  | Actualizează SDK-ul         |
| Selector, aplicația ta       | Pod-uri și module native  | Prefixează sau actualizează |
| Simbol `_nume`, aplicația ta | `nm -m`, apoi pod-urile   | Înlocuiește apelul          |
| Clasă, aplicația ta          | Clasele tale și pod-urile | Prefixează clasa            |

Rândurile „Retrimite un build” semnalează un fals pozitiv, tratat mai jos.

## Pasul 1: deschide build-ul trimis

Lucrează pe fișierul trimis efectiv la Apple, nu pe proiectul tău. Descarcă IPA-ul de pe pagina build-ului de pe expo.dev, apoi rulează pe Mac:

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

A doua linie listează framework-urile incluse. A treia caută o legătură directă către un framework privat Apple: nu trebuie să afișeze nimic. Pe Solys, `otool -L` nu găsește niciun framework privat, nici în executabil, nici în cele 14 framework-uri.

## Pasul 2: găsește fișierul care conține numele

Începe larg. Caută numele citat, fără underscore-urile de la început, în tot bundle-ul:

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

Comanda listează fișierele care conțin textul. Apoi restrânge în funcție de tipul numelui (selector, simbol, clasă), pe fișierul găsit, notat `Binaire`:

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

**Pentru un selector, folosește `strings`, nu `nm`.** Pe framework-ul React din Solys, `nm` afișează metode împreună cu clasa lor, cum ar fi `-[RCTRootView initWithBridge:moduleName:initialProperties:]`, niciodată selectorul `initWithBridge:` singur. `strings` îl găsește.

Pentru un simbol, citește finalul liniei. Pe Solys, `nm -m` afișează de exemplu `(undefined) external __dyld_get_image_name (from libSystem)`: binarul apelează funcția fără s-o conțină, iar libSystem o furnizează. Funcția asta e publică, după cum arată pasul următor.

## Pasul 3: verifică dacă numele e cu adevărat privat

Header-ele SDK-ului iOS descriu API-urile publice. O căutare de câteva secunde îți spune dacă numele citat apare acolo și pe ce platforme. Pentru un simbol, scoate întâi underscore-ul adăugat la compilare: `_SCDynamicStoreCreate` devine `SCDynamicStoreCreate`.

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

Pe 15 octombrie 2026, cu SDK-ul iOS 26.2, iată ce dau patru nume luate din e-mailuri ITMS-90338 reale:

| Nume citat                    | În SDK-ul iOS        | Interpretare         |
| ----------------------------- | -------------------- | -------------------- |
| \_dyld_get_shared_cache_range | Absent               | API privat           |
| boldFontOfSize:               | Absent               | Privat sau coliziune |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios) | Doar macOS           |
| isMainFrame                   | Proprietate WebKit   | Public               |

**Un nume prezent și disponibil pe iOS indică un fals pozitiv. Un nume absent indică un API privat sau o coliziune.** Două limite: un API public doar în Swift nu apare în aceste header-e, iar pentru un accesor ca `setDelegate:`, caută proprietatea `delegate`.

## Pasul 4: ajungi până la pachet

Dacă în cale apare un framework, numele lui te duce la pod, apoi la pachetul npm:

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

`npm explain` îți spune ce dependență l-a instalat, dacă nu l-ai adăugat niciodată tu.

Dacă în cale apare executabilul, caută numele în sursele native, apoi în pod-uri:

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

Adaptează lista de foldere: `modules` e cel al modulelor Expo locale. Folderul `ios/Pods` apare după `npx expo prebuild -p ios`, care instalează și pod-urile pe Mac. Atenție: comanda poate recrea folderul `ios`. Dacă l-ai modificat de mână, lucrează pe o copie.

**Căutarea în `ios/Pods` contează la fel de mult ca cea din surse.** Pe Solys, acest folder conține 15 framework-uri livrate deja compilate, în format `.xcframework`: module Expo, Hermes, React Native și SDK-uri terțe. O căutare limitată la fișierele sursă le-ar rata codul.

## Cauzele tipice

**Un SDK terț, prea vechi sau prea nou.** În septembrie 2026, un dezvoltator Flutter a [raportat o respingere cauzată de un SDK de securitate](https://github.com/talsec/Free-RASP-Flutter/issues/236): versiunea 8.2.3 apela `__dyld_get_shared_cache_range`. Versiunea 8.2.2 trecea, iar editorul a recomandat revenirea la ea până la un fix.

**Un modul nativ care apelează un API nedocumentat.** Propriul tău modul, un plugin sau o bibliotecă mică poate folosi o metodă internă din UIKit găsită pe un forum. Verificarea o detectează după nume.

**O coliziune de nume.** Metoda sau clasa ta poartă, din întâmplare, numele unui API privat Apple. Mesajul prevede asta și îți cere s-o redenumești. Apple recomandă să [prefixezi metodele adăugate la clasele sale](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), de exemplu cu trei litere mici urmate de un underscore.

**Un API public pe macOS, dar nu pe iOS.** În 2024, o aplicație .NET MAUI a fost [respinsă pentru `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple documentează această funcție, dar [doar pentru macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), iar header-ul din SDK-ul iOS o marchează `API_UNAVAILABLE(ios, tvos, watchos)`.

## Rezolvă fără să ocolești

1. **Actualizează dependența.** Pentru pachetele urmărite de Expo, `npx expo install --check` le semnalează pe cele care nu se potrivesc cu SDK-ul tău, conform [documentației Expo CLI](https://docs.expo.dev/more/expo-cli/). Pentru celelalte, citește changelog-ul.
2. **Revino la versiunea anterioară** dacă cea nouă a introdus numele și anunță editorul.
3. **Înlocuiește sau elimină** o bibliotecă abandonată.
4. **Redenumește** metoda sau clasa ta, cu un prefix.

**Nu ascunde numele.** Să spargi un string sau să apelezi metoda pe ocolite ascunde utilizarea fără s-o rezolve. Rămâne interzisă de regula 2.5.1 și de contract, și atunci contul tău e cel expus.

Apoi refă build-ul cu un nou [număr de build](https://devoria.fr/ro/blog/build-number-app-store-connect-versiune). Un update la distanță nu e suficient: numele e în codul nativ compilat. Înainte de upload, rulează din nou pasul 2 pe noul IPA. Numele nu trebuie să mai apară.

## Când e un fals pozitiv

Verificarea Apple mai greșește. Pe 6 iunie 2022, aplicații React Native și Flutter au primit ITMS-90338 pentru nume publice din WebKit, cum ar fi `setNavigationDelegate:` sau `isMainFrame`, inclusiv pe update-uri aproape neschimbate. A doua zi, un inginer Apple a scris că problema fusese rezolvată în partea de validare și a invitat lumea să retrimită build-urile. Thread-ul e păstrat în [repo-ul GitHub al Expo](https://github.com/expo/expo/issues/17768).

Trei indicii luate împreună indică un fals pozitiv:

- build-ul seamănă cu ultimul build acceptat;
- pasul 2 nu găsește numele, sau pasul 3 îl arată public pe iOS;
- alți dezvoltatori raportează aceeași eroare în aceeași zi pe forumurile Apple.

În cazul ăsta, uită-te la [starea serviciilor Apple](https://developer.apple.com/system-status/), apoi retrimite un build cu un număr nou. Dacă eroarea persistă, scrie la [suportul pentru dezvoltatori Apple](https://developer.apple.com/contact/) cu e-mailul complet. O coliziune de nume, în schimb, nu e un fals pozitiv în ochii Apple: redenumește.

## Înainte de următorul upload

Și celelalte erori ITMS au ghidul lor: [ITMS-90725](https://devoria.fr/ro/blog/itms-90725-sdk-version-issue-expo) pentru versiunea de Xcode, [ITMS-91053](https://devoria.fr/ro/blog/itms-91053-missing-api-declaration-expo) pentru privacy manifest și [ITMS-90034](https://devoria.fr/ro/blog/itms-90034-missing-invalid-signature) pentru semnătură. Odată ce build-ul e acceptat, instalează-l cu [TestFlight](https://devoria.fr/ro/blog/testflight-testare-aplicatie-iphone) înainte de review.

Respingerile date de un reviewer cer mai multă distanță: pașii sunt pe pagina [aplicație respinsă de Apple](https://devoria.fr/ro/expertise/aplicatie-respinsa-de-apple), iar motivele cele mai frecvente sunt în [de ce respinge Apple o aplicație](https://devoria.fr/ro/blog/aplicatie-respinsa-apple-cazuri-reale).

Ca să-ți verifici proiectul înainte de upload, [Review Kit](https://devoria.fr/ro/kits/app-review) reunește checklist-ul meu și skill-uri Claude Code care îți recitesc proiectul. Dacă upload-ul ți se blochează și vrei o privire umană, [rezervă o oră cu mine](https://devoria.fr/ro/coaching): reluăm proiectul tău pe video, pe ecranul tău. Iar dacă îți pregătești prima lansare, [fă-ți diagnosticul gratuit](https://devoria.fr/ro/diagnostic): îți pune pașii în ordine și îți trimite checklist-ul meu anti-respingere Apple.

## Întrebări frecvente

### Este ITMS-90338 o respingere de la App Review?

Nu. E o verificare automată făcută în timpul procesării build-ului, înaintea oricărui review uman. Build-ul respins nu ajunge nici în TestFlight, nici în review. Un API privat descoperit mai târziu de un reviewer ține, în schimb, de guideline 2.5.1.

### Pot rezolva ITMS-90338 cu un update la distanță?

Nu. Numele citate sunt în codul nativ compilat al binarului. Un update la distanță schimbă doar JavaScript-ul și resursele. Corectează dependența, refă build-ul, apoi retrimite-l.

### De ce citează Apple metode pe care nu le-am scris niciodată?

Pentru că vin dintr-o bibliotecă inclusă sau dintr-o coliziune: o dependență declară o metodă care poartă numele unui API privat. Apple citește numele prezente în binar, nu codul tău sursă. Ajungi până la pachet cu `grep` și `npm explain`.

### Cum aflu dacă un API e public pe iOS?

Caută-i numele în header-ele SDK-ului iOS, a căror cale ți-o dă `xcrun --sdk iphoneos --show-sdk-path`, apoi citește-i disponibilitatea. Pagina de documentație Apple o indică și ea. O funcție documentată doar pentru macOS nu e publică pe iPhone.

### Trebuie să schimb numărul de build după corectare?

Da. Lasă EAS să-l incrementeze: un număr deja trimis pentru aceeași versiune poate fi respins cu ITMS-90189.


## Proiectul tău

[Review Kit](https://devoria.fr/ro/kits/app-review): Checklistul meu de review App Store, respingerile mele de la Apple rezolvate, checklistul pentru Google Play și trei skilluri Claude Code. 259 RON.

[iOS & Android Kit](https://devoria.fr/ro/kits/ios): Creează-ți aplicația de iPhone și Android cu Claude Code: codul de pornire al aplicațiilor mele, drumul până în store-uri și toate kiturile. 529 RON.

[Diagnostic: ce te desparte de aplicația ta de iPhone](https://devoria.fr/ro/diagnostic): 7 întrebări, 60 de secunde: afli ce te blochează de fapt să-ți publici aplicația de iPhone, traseul tău în 4 etape și kitul potrivit pentru tine.
