# ITMS-90338: как исправить Non-public API usage в Expo

> ITMS-90338: твой билд ссылается на метод, функцию или класс, которые Apple считает приватными. Найди виновную зависимость, обнови её и собери билд заново.

Оригинальная страница: [ITMS-90338: как исправить Non-public API usage в Expo](https://devoria.fr/ru/blog/itms-90338-non-public-api-usage-expo)
Автор: [Mehdi Azizi](https://devoria.fr/ru/mehdi-azizi)
Опубликовано: 2026-11-04
Последняя правка контента: 2026-11-04


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 года Apple ещё предлагает `xcrun swift-demangle`, чтобы сделать закодированные имена Swift читаемыми.

За этой проверкой стоит [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): приложение может использовать только публичные API. [Лицензионное соглашение Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) повторяет это в пункте 3.3.1: никаких вызовов приватных API. Как это правило проверяют живые ревьюеры, подробно разобрано в гайде по [guideline 2.5.1](https://devoria.fr/ru/blog/guideline-2-5-1-public-api-sdk-expo). А здесь мы разбираемся с ошибкой при загрузке.

Как и в случае других ошибок 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                  |
| Селектор, твоё приложение      | Поды и нативные модули    | Добавить префикс или обновить |
| Символ `_nom`, твоё приложение | `nm -m`, потом поды       | Заменить вызов                |
| Класс, твоё приложение         | Твои классы и поды        | Добавить префикс к классу     |

Строки «Отправить билд заново» означают ложное срабатывание, о нём ниже.

## Шаг 1: открыть отправленный билд

Работай с файлом, который реально ушёл в Apple, а не с проектом. Скачай IPA со страницы билда на expo.dev и запусти на Mac:

```bash
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: найти файл, в котором есть это имя

Начни с широкого поиска. Ищи указанное имя без подчёркиваний в начале по всему бандлу:

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

Команда покажет файлы, в которых есть этот текст. Потом уточни поиск в зависимости от типа имени (селектор, символ, класс) в найденном файле, здесь он обозначен как `Binary`:

```bash
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`.

```bash
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:

```bash
grep -n "FrameworkName" ios/Podfile.lock
npm explain package-name
```

`npm explain` покажет, какая зависимость его установила, если ты сам его никогда не добавлял.

Если в пути указан исполняемый файл, ищи имя в нативных исходниках, а потом в подах:

```bash
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 безопасности](https://github.com/talsec/Free-RASP-Flutter/issues/236): версия 8.2.3 вызывала `__dyld_get_shared_cache_range`. Версия 8.2.2 проходила, и разработчик SDK посоветовал откатиться на неё, пока не выйдет исправление.

**Нативный модуль, который вызывает недокументированный API.** Твой собственный модуль, плагин или небольшая библиотека может использовать внутренний метод UIKit, найденный на каком-нибудь форуме. Проверка ловит его по имени.

**Коллизия имён.** Твой метод или класс случайно называется так же, как приватный API Apple. Письмо это предусматривает и просит переименовать. Apple рекомендует [добавлять префикс к методам, которые ты добавляешь в её классы](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), например три строчные буквы и подчёркивание.

**API, публичный на macOS, но не на iOS.** В 2024 году приложение на .NET MAUI [отклонили из-за `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple действительно документирует эту функцию, но [только для macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), а заголовок iOS SDK помечает её как `API_UNAVAILABLE(ios, tvos, watchos)`.

## Исправляем, а не обходим

1. **Обнови зависимость.** Для пакетов, которые отслеживает Expo, `npx expo install --check` покажет те, что не соответствуют твоему SDK, согласно [документации Expo CLI](https://docs.expo.dev/more/expo-cli/). Для остальных читай changelog.
2. **Откатись на предыдущую версию**, если имя появилось в новой, и сообщи об этом разработчику.
3. **Замени или удали** заброшенную библиотеку.
4. **Переименуй** свой метод или класс, добавив префикс.

**Не маскируй имя.** Разбить строку на части или вызвать метод в обход значит спрятать использование, а не исправить его. Это всё равно запрещено правилом 2.5.1 и соглашением, и под удар тогда попадает уже твой аккаунт.

Потом собери билд заново с новым [номером билда](https://devoria.fr/ru/blog/app-store-connect-build-number-version). Обновления по воздуху недостаточно: имя сидит в скомпилированном нативном коде. Перед отправкой снова выполни шаг 2 на новом IPA. Имени там больше быть не должно.

## Когда это ложное срабатывание

Проверка Apple иногда ошибается. 6 июня 2022 года приложения на React Native и Flutter получили ITMS-90338 из-за публичных имён WebKit, например `setNavigationDelegate:` или `isMainFrame`, причём даже на почти не изменённых обновлениях. На следующий день инженер Apple написал, что проблему на стороне валидации исправили, и предложил отправить билды заново. Эта ветка сохранилась в [GitHub-репозитории Expo](https://github.com/expo/expo/issues/17768).

Если совпали все три признака, это скорее всего ложное срабатывание:

- билд похож на последний принятый;
- шаг 2 не находит имя, или шаг 3 показывает, что оно публичное на iOS;
- другие разработчики в тот же день жалуются на ту же ошибку на форумах Apple.

В этом случае проверь [статус сервисов Apple](https://developer.apple.com/system-status/) и отправь билд заново с новым номером. Если ошибка не уходит, напиши в [поддержку разработчиков Apple](https://developer.apple.com/contact/) и приложи письмо целиком. А вот коллизия имён для Apple ложным срабатыванием не считается: переименовывай.

## Перед следующей отправкой

У других ошибок ITMS тоже есть свои гайды: [ITMS-90725](https://devoria.fr/ru/blog/itms-90725-sdk-version-issue-expo) про версию Xcode, [ITMS-91053](https://devoria.fr/ru/blog/itms-91053-missing-api-declaration-expo) про privacy manifest и [ITMS-90034](https://devoria.fr/ru/blog/itms-90034-missing-invalid-signature) про подпись. Когда билд примут, установи его через [TestFlight](https://devoria.fr/ru/blog/testflight-testirovanie-iphone-prilozheniya) до ревью.

Отказы от ревьюера требуют более вдумчивого подхода: порядок действий описан на странице [приложение отклонено Apple](https://devoria.fr/ru/expertise/app-rejected-by-apple), а самые частые причины собраны в статье [почему Apple отклоняет приложения](https://devoria.fr/ru/blog/otkaz-apple-app-store-prichiny).

Чтобы проверить всё перед отправкой, в [Review Kit](https://devoria.fr/ru/kits/app-review) есть мой чек-лист и skills для Claude Code, которые проверяют твой проект. Если отправка застряла и тебе нужен взгляд живого человека, [забронируй час со мной](https://devoria.fr/ru/coaching): разберём твой проект по видеосвязи, прямо на твоём экране. А если ты готовишь первый релиз, [пройди бесплатную диагностику](https://devoria.fr/ru/diagnostic): она расставит шаги по порядку и пришлёт тебе мой чек-лист против отказов 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.


## Твой проект

[Review Kit](https://devoria.fr/ru/kits/app-review): Мой чек-лист для ревью в App Store, исправленные отказы Apple, чек-лист для Google Play и три скилла для Claude Code. 55 $.

[iOS & Android Kit](https://devoria.fr/ru/kits/ios): Создай своё приложение для iPhone и Android с Claude Code: стартовый код моих приложений, путь до публикации в сторах и все киты. 109 $.

[Диагностика: что отделяет тебя от твоего iPhone-приложения](https://devoria.fr/ru/diagnostic): 7 вопросов, 60 секунд: ты узнаешь, что на самом деле мешает тебе опубликовать iPhone-приложение, получишь путь из 4 этапов и подходящий тебе кит.
