ITMS-90338: jak naprawić „Non-public API usage” w Expo
Autor: Mehdi Azizi
Opublikowano , 9 min
ITMS-90338: twój build odwołuje się do metody, funkcji lub klasy, którą Apple uznaje za prywatną. Znajdź winną zależność, zaktualizuj ją i zrób nowy build.
ITMS-90338 to automatyczna kontrola Apple na twoim buildzie. Mówi, że twój plik binarny odwołuje się do API, które Apple uważa za prywatne: metod Objective-C, funkcji albo klas. Rozwiązanie: znajdź plik wskazany w mailu, dojdź do zależności, która zawiera tę nazwę, zaktualizuj ją albo zmień nazwę swojej metody, a potem zrób nowy build.
W Expo ta nazwa prawie zawsze pochodzi z natywnej biblioteki, rzadko z twojego JavaScriptu. 15 października 2026 uruchomiłem polecenia z tego poradnika na buildzie symulatora Solys, mojego budzika z minigrami (Expo SDK 57), oraz na iOS SDK z Xcode 26.2. Oto jak czytać maila, znaleźć winowajcę i rozpoznać fałszywy alarm.
Co oznacza błąd ITMS-90338?
Wiadomość ma tytuł „ITMS-90338: Non-public API usage”. Występuje w trzech wariantach, zależnie od tego, co Apple znalazło w twoim pliku binarnym:
- „references non-public selectors”: nazwy metod Objective-C, np.
boldFontOfSize:; - „references non-public symbols”: funkcje zapisane z podkreślnikiem na początku, np.
__dyld_get_shared_cache_range; - „contains or inherits from non-public classes”: nazwa klasy.
Każdy wariant podaje ścieżkę, a potem listę nazw. Tekst dodaje dwie rady: zmień nazwy swoich metod, jeśli pokrywają się z prywatnym API, i usuń bibliotekę statyczną, która je zawiera. Mail z września 2026 proponuje też xcrun swift-demangle, żeby odczytać zakodowane nazwy Swift.
Zasada stojąca za tą kontrolą to guideline 2.5.1: aplikacja może używać tylko publicznych API. Umowa licencyjna Apple Developer Program powtarza to w punkcie 3.3.1: żadnych wywołań prywatnych API. Ręczną weryfikację tej zasady przez recenzenta opisuję w poradniku o guideline 2.5.1. Tutaj zajmujemy się błędem wysyłki.
Jak przy innych błędach ITMS, nikt nie otwierał twojej aplikacji: Apple przeanalizowało plik binarny podczas przetwarzania buildu. Apple nie publikuje żadnej listy prywatnych API: nazwy z twojego maila to jedyna wiarygodna lista.
Czytanie ścieżki: SDK czy twój plik wykonywalny?
Ścieżka podana przed listą mówi ci, w którym pliku szukać.
Frameworks/Nom.framework/Nom: nazwa jest w osadzonym frameworku dynamicznym, czyli w konkretnym SDK. W buildzie Solys ten folder zawiera 14 frameworków: Hermes, React Native, prekompilowane moduły Expo i bibliotekę obrazów SDWebImage.- Sama nazwa twojej aplikacji: nazwa jest w głównym pliku wykonywalnym. W Expo łączy on twój kod natywny i pody skompilowane ze źródeł, domyślnie linkowane statycznie. Winna może być wtedy dowolna natywna biblioteka.
Drzewo decyzji: od podanej nazwy do poprawki
Idź po wierszach po kolei. Pierwszy, który pasuje do twojego przypadku, wskazuje trop.
| Co widzisz | Gdzie szukać | Poprawka |
|---|---|---|
Nazwa z $s lub _$s | xcrun swift-demangle | Przejdź drzewo od nowa |
| Brak w twoim IPA | grep na bundlu | Wyślij build ponownie |
| Nazwa publiczna na iOS | Nagłówki iOS SDK | Wyślij build ponownie |
API_UNAVAILABLE(ios) | Nagłówki iOS SDK | Zaktualizuj pakiet |
W Frameworks/X | SDK, które dostarcza X | Zaktualizuj SDK |
| Selektor, twoja aplikacja | Pody i moduły natywne | Dodaj prefiks lub zaktualizuj |
Symbol _nazwa, twoja aplikacja | nm -m, potem pody | Zastąp wywołanie |
| Klasa, twoja aplikacja | Twoje klasy i pody | Dodaj prefiks do klasy |
Wiersze „Wyślij build ponownie” oznaczają fałszywy alarm, omówiony niżej.
Krok 1: otwórz wysłany build
Pracuj na pliku faktycznie wysłanym do Apple, nie na swoim projekcie. Pobierz IPA ze strony buildu na expo.dev i uruchom na Macu:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
Druga linia wypisuje osadzone frameworki. Trzecia szuka bezpośredniego linku do prywatnego frameworka Apple: nie powinna nic wyświetlić. W Solys otool -L nie znajduje żadnego prywatnego frameworka, ani w pliku wykonywalnym, ani w 14 frameworkach.
Krok 2: znajdź plik zawierający nazwę
Zacznij szeroko. Szukaj podanej nazwy, bez początkowych podkreślników, w całym bundlu:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
Polecenie wypisuje pliki zawierające ten tekst. Potem zawęź wyszukiwanie zależnie od typu nazwy (selektor, symbol, klasa) w znalezionym pliku, oznaczonym jako Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Dla selektora używaj strings, nie nm. Na frameworku React w Solys nm pokazuje metody razem z klasą, np. -[RCTRootView initWithBridge:moduleName:initialProperties:], nigdy sam selektor initWithBridge:. strings go znajduje.
Przy symbolu czytaj koniec linii. W Solys nm -m pokazuje na przykład (undefined) external __dyld_get_image_name (from libSystem): plik binarny wywołuje funkcję, ale jej nie zawiera, dostarcza ją libSystem. Ta akurat funkcja jest publiczna, co pokazuje następny krok.
Krok 3: sprawdź, czy nazwa jest naprawdę prywatna
Nagłówki iOS SDK opisują publiczne API. Kilkusekundowe wyszukiwanie mówi, czy podana nazwa się tam znajduje i na jakich platformach. Przy symbolu najpierw usuń podkreślnik dodany przy kompilacji: _SCDynamicStoreCreate staje się SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
15 października 2026, z iOS SDK 26.2, oto wynik dla czterech nazw z prawdziwych maili ITMS-90338:
| Podana nazwa | W iOS SDK | Interpretacja |
|---|---|---|
| _dyld_get_shared_cache_range | Brak | Prywatne API |
| boldFontOfSize: | Brak | Prywatne lub kolizja |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Tylko macOS |
| isMainFrame | Właściwość WebKit | Publiczne |
Nazwa obecna i dostępna na iOS wskazuje na fałszywy alarm. Brak nazwy wskazuje na prywatne API albo kolizję. Dwa ograniczenia: publiczne API dostępne tylko w Swift nie pojawia się w tych nagłówkach, a przy akcesorze typu setDelegate: szukaj właściwości delegate.
Krok 4: dojdź do pakietu
Jeśli ścieżka wskazuje framework, jego nazwa prowadzi do poda, a potem do pakietu npm:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain mówi, która zależność go zainstalowała, jeśli sam go nigdy nie dodałeś.
Jeśli ścieżka wskazuje plik wykonywalny, szukaj nazwy w źródłach natywnych, a potem w podach:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Dostosuj listę folderów: modules to folder lokalnych modułów Expo. Folder ios/Pods pojawia się po npx expo prebuild -p ios, które na Macu instaluje też pody. Uwaga: to polecenie może odtworzyć folder ios. Jeśli zmieniałeś go ręcznie, pracuj na kopii.
Przeszukanie ios/Pods jest tak samo ważne jak przeszukanie źródeł. W Solys ten folder zawiera 15 prekompilowanych frameworków w formacie .xcframework: moduły Expo, Hermes, React Native i zewnętrzne SDK. Wyszukiwanie ograniczone do plików źródłowych przeoczyłoby ich kod.
Typowe przyczyny
Zewnętrzne SDK, za stare albo za nowe. We wrześniu 2026 developer Fluttera zgłosił odrzucenie przez SDK bezpieczeństwa: jego wersja 8.2.3 wywoływała __dyld_get_shared_cache_range. Wersja 8.2.2 przechodziła, a wydawca doradził powrót do niej do czasu poprawki.
Moduł natywny wywołujący nieudokumentowane API. Twój własny moduł, plugin albo mała biblioteka może używać wewnętrznej metody UIKit znalezionej na forum. Kontrola wyłapuje ją po nazwie.
Kolizja nazw. Twoja metoda albo klasa przypadkiem nosi nazwę prywatnego API Apple. Wiadomość to przewiduje i prosi o zmianę nazwy. Apple zaleca dodawanie prefiksu do metod dodawanych do jego klas, na przykład trzech małych liter i podkreślnika.
API publiczne na macOS, ale nie na iOS. W 2024 aplikacja .NET MAUI została odrzucona za _SCDynamicStoreCreate. Apple dokumentuje tę funkcję, ale tylko dla macOS, a nagłówek iOS SDK oznacza ją jako API_UNAVAILABLE(ios, tvos, watchos).
Naprawiaj, nie obchodź
- Zaktualizuj zależność. Dla pakietów śledzonych przez Expo
npx expo install --checkwskazuje te, które nie pasują do twojego SDK, zgodnie z dokumentacją Expo CLI. Przy pozostałych przeczytaj changelog. - Wróć do poprzedniej wersji, jeśli to nowa wprowadziła tę nazwę, i daj znać wydawcy.
- Zastąp albo usuń porzuconą bibliotekę.
- Zmień nazwę swojej metody lub klasy, dodając prefiks.
Nie ukrywaj nazwy. Dzielenie stringa albo wywoływanie metody okrężną drogą ukrywa użycie, ale go nie naprawia. Nadal jest zakazane przez zasadę 2.5.1 i umowę, a ryzykujesz wtedy swoim kontem.
Potem zrób nowy build z nowym numerem buildu. Aktualizacja zdalna nie wystarczy: nazwa jest w skompilowanym kodzie natywnym. Przed wysyłką powtórz krok 2 na nowym IPA. Nazwa nie może się już pojawiać.
Kiedy to fałszywy alarm
Kontrola Apple czasem się myli. 6 czerwca 2022 aplikacje React Native i Flutter dostały ITMS-90338 za publiczne nazwy z WebKit, takie jak setNavigationDelegate: czy isMainFrame, także przy prawie niezmienionych aktualizacjach. Następnego dnia inżynier Apple napisał, że problem po stronie walidacji został rozwiązany, i poprosił o ponowne wysłanie buildów. Wątek jest zachowany w repozytorium Expo na GitHubie.
Trzy wskazówki naraz wskazują na fałszywy alarm:
- build jest podobny do ostatniego zaakceptowanego;
- krok 2 nie znajduje nazwy albo krok 3 pokazuje, że jest publiczna na iOS;
- inni developerzy zgłaszają ten sam błąd tego samego dnia na forach Apple.
W takim przypadku sprawdź status usług Apple, a potem wyślij build z nowym numerem. Jeśli błąd się utrzymuje, napisz do wsparcia dla developerów Apple, załączając pełnego maila. Kolizja nazw natomiast nie jest dla Apple fałszywym alarmem: zmień nazwę.
Przed kolejną wysyłką
Inne błędy ITMS też mają swoje poradniki: ITMS-90725 dla wersji Xcode, ITMS-91053 dla manifestu prywatności i ITMS-90034 dla podpisu. Gdy build zostanie zaakceptowany, zainstaluj go przez TestFlight przed recenzją.
Odrzucenia przez recenzenta wymagają więcej dystansu: sposób postępowania opisuję na stronie aplikacja odrzucona przez Apple, a najczęstsze powody w artykule dlaczego Apple odrzuca aplikacje.
Żeby sprawdzić swoje zgłoszenie przed wysyłką, Review Kit zbiera moją checklistę i skille Claude Code, które przeglądają twój projekt. Jeśli wysyłka utknęła i chcesz, żeby spojrzał na to człowiek, zarezerwuj ze mną godzinę: przejdziemy przez twój projekt na wideorozmowie, na twoim ekranie. A jeśli przygotowujesz swoje pierwsze wydanie, zrób darmową diagnozę: ułoży kroki we właściwej kolejności i wyśle ci moją checklistę przeciw odrzuceniom przez Apple.
Najczęściej zadawane pytania
Czy ITMS-90338 to odrzucenie przez App Review?
Nie. To automatyczna kontrola wykonywana podczas przetwarzania buildu, przed jakąkolwiek recenzją przez człowieka. Odrzucony build nie trafia ani do TestFlight, ani do recenzji. Prywatne API wykryte później przez recenzenta podlega natomiast guideline 2.5.1.
Czy można naprawić ITMS-90338 aktualizacją zdalną?
Nie. Podane nazwy są w skompilowanym kodzie natywnym pliku binarnego. Aktualizacja zdalna zmienia tylko JavaScript i zasoby. Napraw zależność, zrób nowy build i wyślij go ponownie.
Dlaczego Apple wskazuje metody, których nigdy nie napisałem?
Bo pochodzą z osadzonej biblioteki albo z kolizji: zależność deklaruje metodę o nazwie prywatnego API. Apple czyta nazwy obecne w pliku binarnym, nie twój kod źródłowy. Dojdź do pakietu za pomocą grep i npm explain.
Jak sprawdzić, czy API jest publiczne na iOS?
Wyszukaj jego nazwę w nagłówkach iOS SDK, których ścieżkę podaje xcrun --sdk iphoneos --show-sdk-path, i sprawdź dostępność. Strona dokumentacji Apple też to pokazuje. Funkcja udokumentowana tylko dla macOS nie jest publiczna na iPhonie.
Czy po poprawce trzeba zmienić numer buildu?
Tak. Pozwól EAS go zwiększyć: numer już wysłany dla tej samej wersji może zostać odrzucony z błędem ITMS-90189.

