ITMS-90338: виправляємо «Non-public API usage» в Expo
Автор: Mehdi Azizi
Опубліковано , 9 хв
ITMS-90338: твій білд згадує метод, функцію чи клас, які Apple вважає приватними. Знайди залежність, онови її і збери білд заново.
ITMS-90338 означає, що Apple автоматично перевірила твій білд. Помилка каже, що твій бінарник посилається на API, які Apple вважає приватними: методи Objective-C, функції або класи. Як виправити: знайди файл, згаданий у листі, дійди до залежності, яка містить цю назву, онови її або перейменуй свій метод, а потім збери білд заново.
В Expo ця назва майже завжди походить із нативної бібліотеки і рідко з твого JavaScript. 15 жовтня 2026 року я запустив команди аналізу з цього гайду на білді для симулятора Solys, мого будильника з мініграми (Expo SDK 57), і на iOS SDK з Xcode 26.2. Нижче розповідаю, як читати лист, як знайти винуватця і як розпізнати хибне спрацювання.
Що означає помилка ITMS-90338?
Повідомлення має заголовок «ITMS-90338: Non-public API usage». Воно буває у трьох варіантах, залежно від того, що Apple знайшла у твоєму бінарнику:
- «references non-public selectors»: назви методів Objective-C, наприклад
boldFontOfSize:; - «references non-public symbols»: функції з підкресленням на початку, наприклад
__dyld_get_shared_cache_range; - «contains or inherits from non-public classes»: назва класу.
У кожному варіанті є шлях, а за ним список назв. У тексті є дві поради: перейменувати свої методи, якщо їхня назва збігається з приватним API, і прибрати статичну бібліотеку, яка їх містить. Лист від вересня 2026 року ще й пропонує xcrun swift-demangle, щоб зробити читабельними закодовані назви Swift.
За цією перевіркою стоїть guideline 2.5.1: застосунок може використовувати лише публічні API. Ліцензійна угода Apple Developer Program повторює це в пункті 3.3.1: жодних викликів приватних API. Ручну перевірку за цим правилом детально розібрано в гайді про guideline 2.5.1. Тут ми розбираємо помилку відправлення.
Як і з іншими помилками ITMS, твій застосунок ніхто не відкривав: Apple проаналізувала бінарник під час обробки білда. Apple не публікує списку приватних API: назви з твого листа і є єдиним надійним списком.
Читаємо шлях: SDK чи твій виконуваний файл?
Шлях перед списком підказує, в якому файлі шукати.
Frameworks/Name.framework/Name: назва міститься у вбудованому динамічному фреймворку, тобто в конкретному SDK. У білді Solys у цій папці 14 фреймворків: Hermes, React Native, уже скомпільовані модулі Expo та бібліотека зображень SDWebImage.- Лише назва твого застосунку: назва міститься в головному виконуваному файлі. В Expo він об'єднує твій нативний код і поди, скомпільовані з вихідного коду, які за замовчуванням лінкуються статично. Тоді винною може бути будь-яка нативна бібліотека.
Дерево рішень: від згаданої назви до виправлення
Йди рядками по порядку. Перший рядок, що підходить до твого випадку, і дає підказку.
| Що ти бачиш | Де шукати | Виправлення |
|---|---|---|
Назва з $s або _$s | xcrun swift-demangle | Пройти дерево ще раз |
| Немає у твоєму IPA | grep по бандлу | Надіслати білд ще раз |
| Публічна назва в iOS | Заголовки iOS SDK | Надіслати білд ще раз |
API_UNAVAILABLE(ios) | Заголовки iOS SDK | Оновити пакет |
У Frameworks/X | SDK, що постачає X | Оновити SDK |
| Селектор, твій застосунок | Поди й нативні модулі | Додати префікс чи оновити |
Символ _name, твій застосунок | nm -m, потім поди | Замінити виклик |
| Клас, твій застосунок | Твої класи й поди | Додати префікс до класу |
Рядки «Надіслати білд ще раз» вказують на хибне спрацювання, про нього нижче.
Крок 1: відкрити надісланий білд
Працюй із файлом, який справді надіслано в Apple, а не з проєктом. Завантаж IPA зі сторінки білда на expo.dev і запусти на Mac:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
Другий рядок показує вбудовані фреймворки. Третій шукає пряме посилання на приватний фреймворк Apple: він не повинен нічого вивести. У Solys otool -L не знаходить жодного приватного фреймворку ні у виконуваному файлі, ні в 14 фреймворках.
Крок 2: знайти файл, що містить назву
Почни широко. Шукай згадану назву, без підкреслень на початку, по всьому бандлу:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
Команда виводить файли, що містять цей текст. Далі уточнюй залежно від типу назви (селектор, символ, клас) у знайденому файлі, позначеному як Binary:
strings -a Binary | grep -x "boldFontOfSize:"
nm -m Binary | grep "_dyld_get_shared_cache_range"
nm -m Binary | grep '_OBJC_CLASS_$_ClassName'
Для селектора використовуй strings, а не nm. На фреймворку React у Solys nm показує методи разом із класом, наприклад -[RCTRootView initWithBridge:moduleName:initialProperties:], і ніколи окремо селектор initWithBridge:. А strings його знаходить.
Для символу дивись на кінець рядка. У Solys nm -m показує, наприклад, (undefined) external __dyld_get_image_name (from libSystem): бінарник викликає функцію, але не містить її, а постачає її libSystem. Саме ця функція публічна, як покаже наступний крок.
Крок 3: перевірити, чи назва справді приватна
Заголовки iOS SDK описують публічні API. Пошук на кілька секунд покаже, чи є там згадана назва і для яких платформ. Для символу спершу прибери підкреслення, додане під час компіляції: _SCDynamicStoreCreate стає SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Ось що 15 жовтня 2026 року з iOS SDK 26.2 дали чотири назви з реальних листів ITMS-90338:
| Згадана назва | В iOS SDK | Висновок |
|---|---|---|
| _dyld_get_shared_cache_range | Відсутня | Приватний API |
| boldFontOfSize: | Відсутня | Приватний або колізія |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Лише macOS |
| isMainFrame | Властивість WebKit | Публічний |
Якщо назва є і доступна в iOS, це вказує на хибне спрацювання. Якщо її немає, це вказує на приватний API або колізію. Два обмеження: API, публічний лише у Swift, у цих заголовках не з'являється, а для аксесора на кшталт setDelegate: шукай властивість delegate.
Крок 4: дійти до пакета
Якщо шлях згадує фреймворк, його назва веде до пода, а потім до npm-пакета:
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
npm explain покаже, яка залежність його встановила, якщо ти сам його ніколи не додавав.
Якщо шлях згадує виконуваний файл, шукай назву в нативних вихідних файлах, а потім у подах:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Підлаштуй список папок: modules містить локальні модулі Expo. Папка ios/Pods з'являється після npx expo prebuild -p ios, яка на Mac ще й встановлює поди. Увага: ця команда може перестворити папку ios. Якщо ти змінював її вручну, працюй із копією.
Пошук в ios/Pods важить не менше, ніж пошук у вихідному коді. У Solys ця папка містить 15 уже скомпільованих фреймворків у форматі .xcframework: модулі Expo, Hermes, React Native і сторонні SDK. Пошук лише по вихідних файлах пропустить їхній код.
Типові причини
Сторонній SDK, надто старий або надто новий. У вересні 2026 року Flutter-розробник повідомив про відмову через SDK безпеки: його версія 8.2.3 викликала __dyld_get_shared_cache_range. Версія 8.2.2 проходила, і розробник SDK порадив повернутися до неї, поки не вийде виправлення.
Нативний модуль, що викликає недокументований API. Твій власний модуль, плагін чи невелика бібліотека може використовувати внутрішній метод UIKit, знайдений на форумі. Перевірка знаходить його за назвою.
Колізія назв. Твій метод чи клас випадково має ту саму назву, що й приватний API Apple. Повідомлення це передбачає і просить перейменувати. Apple радить додавати префікс до методів, які ти додаєш до її класів, наприклад три малі літери й підкреслення.
API, публічний у macOS, але не в iOS. У 2024 році застосунок на .NET MAUI відхилили через _SCDynamicStoreCreate. Apple документує цю функцію, але лише для macOS, а заголовок iOS SDK позначає її API_UNAVAILABLE(ios, tvos, watchos).
Виправляти, а не обходити
- Онови залежність. Для пакетів, які відстежує Expo,
npx expo install --checkпоказує ті, що не відповідають твоєму SDK, згідно з документацією Expo CLI. Для інших читай changelog. - Повернися до попередньої версії, якщо назву принесла нова, і повідом розробника.
- Заміни або прибери покинуту бібліотеку.
- Перейменуй свій метод чи клас, додавши префікс.
Не маскуй назву. Розбити рядок на частини чи викликати метод обхідним шляхом означає сховати використання, а не виправити його. Воно все одно заборонене правилом 2.5.1 і угодою, і тоді під ударом опиняється твій акаунт.
Потім збери білд знову з новим номером білда. Віддаленого оновлення не вистачить: назва сидить у скомпільованому нативному коді. Перед відправленням повтори крок 2 на новому IPA. Назви там бути не повинно.
Коли це хибне спрацювання
Перевірка Apple іноді помиляється. 6 червня 2022 року застосунки на React Native і Flutter отримали ITMS-90338 за публічні назви WebKit, як-от setNavigationDelegate: або isMainFrame, навіть на майже незмінених оновленнях. Наступного дня інженер Apple написав, що проблему на боці валідації вирішено, і запропонував надіслати білди ще раз. Обговорення збереглося в GitHub-репозиторії Expo.
Три ознаки разом вказують на хибне спрацювання:
- білд схожий на останній прийнятий;
- крок 2 не знаходить назви, або крок 3 показує, що вона публічна в iOS;
- інші розробники того ж дня пишуть про ту саму помилку на форумах Apple.
У такому разі перевір стан сервісів Apple, а потім надішли білд із новим номером. Якщо помилка не зникає, напиши в підтримку розробників Apple і додай повний лист. А от колізія назв для Apple хибним спрацюванням не є: перейменовуй.
Перед наступним відправленням
Для інших помилок ITMS теж є гайди: ITMS-90725 про версію Xcode, ITMS-91053 про маніфест конфіденційності та ITMS-90034 про підпис. Коли білд приймуть, встанови його через TestFlight перед перевіркою.
Відмови від рев'юера потребують більш виваженого підходу: як діяти, описано на сторінці застосунок відхилено Apple, а найчастіші причини зібрано в статті чому Apple відхиляє застосунок.
Щоб перевірити все перед відправленням, Review Kit містить мій чекліст і skills для Claude Code, які переглядають твій проєкт. Якщо відправлення застрягло і тобі потрібен погляд живої людини, забронюй годину зі мною: розберемо твій проєкт по відео, на твоєму екрані. А якщо готуєш свій перший реліз, пройди безкоштовну діагностику: вона розставить кроки по порядку і надішле тобі мій чекліст проти відмов Apple.
Поширені запитання
ITMS-90338 це відмова App Review?
Ні. Це автоматична перевірка під час обробки білда, ще до будь-якої ручної перевірки. Відхилений білд не потрапляє ні в TestFlight, ні на рев'ю. А от приватний API, який пізніше помітить рев'юер, підпадає під guideline 2.5.1.
Чи можна виправити ITMS-90338 віддаленим оновленням?
Ні. Згадані назви містяться в скомпільованому нативному коді бінарника. Віддалене оновлення змінює лише JavaScript і ресурси. Виправ залежність, збери білд заново і надішли його.
Чому Apple згадує методи, яких я ніколи не писав?
Бо вони походять із вбудованої бібліотеки або з колізії: залежність оголошує метод, назва якого збігається з приватним API. Apple читає назви в бінарнику, а не твій вихідний код. Дійди до пакета за допомогою grep і npm explain.
Як дізнатися, чи API публічний в iOS?
Шукай його назву в заголовках iOS SDK, шлях до яких дає xcrun --sdk iphoneos --show-sdk-path, і дивись на доступність. Сторінка документації Apple теж це вказує. Функція, задокументована лише для macOS, не є публічною на iPhone.
Чи треба змінювати номер білда після виправлення?
Так. Нехай EAS його збільшить: номер, уже надісланий для тієї самої версії, можуть відхилити з помилкою ITMS-90189.

