# ITMS-90338: corregir “Non-public API usage” con Expo

> ITMS-90338: tu build cita una API que Apple considera privada. Encuentra la dependencia, actualízala y vuelve a hacer un build.

Página original: [ITMS-90338: corregir “Non-public API usage” con Expo](https://devoria.fr/es/blog/itms-90338-non-public-api-usage-expo)
Autor: [Mehdi Azizi](https://devoria.fr/es/mehdi-azizi)
Publicación: 2026-11-04
Última revisión del contenido: 2026-11-04


ITMS-90338 es un control automático de Apple sobre tu build. Indica que tu binario hace referencia a API que Apple considera privadas: métodos Objective-C, funciones o clases. **La solución: ubicar el archivo citado en el correo, rastrear la dependencia que contiene el nombre, actualizarla o renombrar tu método, y luego hacer un nuevo build.**

Con Expo, el nombre casi siempre viene de una biblioteca nativa, rara vez de tu JavaScript. El 15 de octubre de 2026 ejecuté los comandos de análisis de esta guía sobre un build de simulador de Solys, mi despertador con minijuegos (SDK de Expo 57), y sobre el SDK de iOS de Xcode 26.2. Aquí te explico cómo leer el correo, encontrar al culpable y reconocer un falso positivo.

## ¿Qué significa el error ITMS-90338?

El mensaje se titula "ITMS-90338: Non-public API usage". Existe en tres variantes, según lo que Apple encontró en tu binario:

- **"references non-public selectors"**: nombres de métodos Objective-C, como `boldFontOfSize:`;
- **"references non-public symbols"**: funciones, escritas con un guion bajo delante, como `__dyld_get_shared_cache_range`;
- **"contains or inherits from non-public classes"**: un nombre de clase.

Cada variante da una ruta y luego la lista de nombres. El texto agrega dos consejos: renombrar tus métodos si su nombre coincide con una API privada, y quitar la biblioteca estática que los contiene. Un correo de septiembre de 2026 también propone `xcrun swift-demangle` para hacer legibles los nombres Swift codificados.

La regla detrás de este control es la [guideline 2.5.1](https://developer.apple.com/app-store/review/guidelines/#2.5.1): una app solo puede usar API públicas. El [contrato de licencia del Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) lo repite en su artículo 3.3.1: ninguna llamada a una API privada. La revisión humana de esta regla se detalla en la guía de la [guideline 2.5.1](https://devoria.fr/es/blog/guideline-2-5-1-api-publicas-sdk-expo). Aquí tratamos el error de envío.

Como con los demás errores ITMS, nadie abrió tu app: Apple analizó el binario durante el procesamiento del build. **Apple no publica ninguna lista de API privadas: los nombres de tu correo son la única lista confiable.**

## Leer la ruta: ¿un SDK o tu ejecutable?

La ruta escrita antes de la lista te dice en qué archivo buscar.

- **`Frameworks/Nom.framework/Nom`**: el nombre está en un framework dinámico incluido, es decir, en un SDK concreto. En el build de Solys, esta carpeta contiene 14 frameworks: Hermes, React Native, módulos de Expo entregados ya compilados y la biblioteca de imágenes SDWebImage.
- **Solo el nombre de tu app**: el nombre está en el ejecutable principal. Con Expo, este reúne tu código nativo y los pods compilados desde sus fuentes, enlazados de forma estática por defecto. Cualquier biblioteca nativa puede ser la responsable.

## El árbol: del nombre citado a la solución

Recorre las filas en orden. La primera que coincida con tu caso te da la pista.

| Lo que lees               | Dónde buscar            | Solución                     |
| ------------------------- | ----------------------- | ---------------------------- |
| Nombre en `$s` o `_$s`    | `xcrun swift-demangle`  | Volver al árbol              |
| No aparece en tu IPA      | `grep` sobre el bundle  | Reenviar un build            |
| Nombre público en iOS     | Encabezados del SDK iOS | Reenviar un build            |
| `API_UNAVAILABLE(ios)`    | Encabezados del SDK iOS | Actualizar el paquete        |
| En `Frameworks/X`         | El SDK que provee X     | Actualizar el SDK            |
| Selector, tu app          | Pods y módulos nativos  | Agregar prefijo o actualizar |
| Símbolo `_nombre`, tu app | `nm -m`, luego los pods | Reemplazar la llamada        |
| Clase, tu app             | Tus clases y tus pods   | Agregar prefijo a la clase   |

Las filas "Reenviar un build" señalan un falso positivo, que se trata más abajo.

## Paso 1: abrir el build enviado

Trabaja sobre el archivo realmente enviado a Apple, no sobre tu proyecto. Descarga el IPA desde la página del build en expo.dev y luego ejecuta en tu Mac:

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

La segunda línea lista los frameworks incluidos. La tercera busca un enlace directo a un framework privado de Apple: no debe mostrar nada. En Solys, `otool -L` no encuentra ningún framework privado, ni en el ejecutable ni en los 14 frameworks.

## Paso 2: encontrar el archivo que contiene el nombre

Empieza amplio. Busca el nombre citado, sin los guiones bajos iniciales, en todo el bundle:

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

El comando lista los archivos que contienen ese texto. Luego afina según el tipo de nombre (selector, símbolo, clase), sobre el archivo encontrado, indicado como `Binary`:

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

**Para un selector, usa `strings`, no `nm`.** En el framework React de Solys, `nm` muestra métodos con su clase, como `-[RCTRootView initWithBridge:moduleName:initialProperties:]`, nunca el selector `initWithBridge:` solo. `strings` sí lo encuentra.

Para un símbolo, lee el final de la línea. En Solys, `nm -m` muestra por ejemplo `(undefined) external __dyld_get_image_name (from libSystem)`: el binario llama a la función sin contenerla, y libSystem la provee. Esa función es pública, como muestra el siguiente paso.

## Paso 3: verificar si el nombre es realmente privado

Los encabezados del SDK de iOS describen las API públicas. Una búsqueda de unos segundos te dice si el nombre citado aparece ahí, y en qué plataformas. Para un símbolo, primero quita el guion bajo agregado en la compilación: `_SCDynamicStoreCreate` pasa a ser `SCDynamicStoreCreate`.

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

El 15 de octubre de 2026, con el SDK de iOS 26.2, esto es lo que dan cuatro nombres tomados de correos ITMS-90338 reales:

| Nombre citado                 | En el SDK de iOS     | Lectura            |
| ----------------------------- | -------------------- | ------------------ |
| \_dyld_get_shared_cache_range | Ausente              | API privada        |
| boldFontOfSize:               | Ausente              | Privada o colisión |
| SCDynamicStoreCreate          | API_UNAVAILABLE(ios) | Solo macOS         |
| isMainFrame                   | Propiedad de WebKit  | Pública            |

**Un nombre presente y disponible en iOS apunta a un falso positivo. Un nombre ausente apunta a una API privada o a una colisión.** Dos límites: una API pública solo en Swift no aparece en estos encabezados, y para un accesor como `setDelegate:`, busca la propiedad `delegate`.

## Paso 4: llegar hasta el paquete

Si la ruta cita un framework, su nombre te lleva al pod y luego al paquete npm:

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

`npm explain` te dice qué dependencia lo instaló, si nunca lo agregaste tú.

Si la ruta cita el ejecutable, busca el nombre en las fuentes nativas y luego en los pods:

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

Ajusta la lista de carpetas: `modules` es la de los módulos locales de Expo. La carpeta `ios/Pods` aparece después de `npx expo prebuild -p ios`, que también instala los pods en Mac. Cuidado: puede volver a crear la carpeta `ios`. Si la modificaste a mano, trabaja sobre una copia.

**La búsqueda en `ios/Pods` cuenta tanto como la de las fuentes.** En Solys, esta carpeta contiene 15 frameworks entregados ya compilados, en formato `.xcframework`: módulos de Expo, Hermes, React Native y SDK de terceros. Una búsqueda limitada a los archivos fuente pasaría por alto su código.

## Las causas típicas

**Un SDK de terceros, demasiado antiguo o demasiado reciente.** En septiembre de 2026, un desarrollador de Flutter [reportó un rechazo causado por un SDK de seguridad](https://github.com/talsec/Free-RASP-Flutter/issues/236): su versión 8.2.3 llamaba a `__dyld_get_shared_cache_range`. La versión 8.2.2 pasaba, y el proveedor recomendó volver a ella mientras llegaba una corrección.

**Un módulo nativo que llama a una API no documentada.** Tu propio módulo, un plugin o una pequeña biblioteca puede usar un método interno de UIKit encontrado en un foro. El control lo detecta por su nombre.

**Una colisión de nombre.** Tu método o tu clase lleva, por casualidad, el nombre de una API privada de Apple. El mensaje lo contempla y te pide renombrarlo. Apple recomienda [agregar un prefijo a los métodos añadidos a sus clases](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), por ejemplo tres letras minúsculas seguidas de un guion bajo.

**Una API pública en macOS, pero no en iOS.** En 2024, una app .NET MAUI fue [rechazada por `_SCDynamicStoreCreate`](https://github.com/dotnet/runtime/issues/103949). Apple sí documenta esta función, pero [solo para macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), y el encabezado del SDK de iOS la marca como `API_UNAVAILABLE(ios, tvos, watchos)`.

## Corregir sin hacer trampa

1. **Actualiza la dependencia.** Para los paquetes que sigue Expo, `npx expo install --check` señala los que no corresponden a tu SDK, según la [documentación de Expo CLI](https://docs.expo.dev/more/expo-cli/). Para los demás, lee el historial de versiones.
2. **Vuelve a la versión anterior** si fue la nueva la que introdujo el nombre, y avisa al proveedor.
3. **Reemplaza o quita** una biblioteca abandonada.
4. **Renombra** tu método o tu clase, con un prefijo.

**No ocultes el nombre.** Partir una cadena o llamar al método por un rodeo esconde el uso sin corregirlo. Sigue prohibido por la regla 2.5.1 y por el contrato, y entonces es tu cuenta la que queda expuesta.

Después, haz un nuevo build con un nuevo [número de build](https://devoria.fr/es/blog/app-store-connect-build-number-version). Una actualización remota no basta: el nombre está en el código nativo compilado. Antes del envío, vuelve a ejecutar el paso 2 sobre el nuevo IPA. El nombre ya no debe aparecer.

## Cuando es un falso positivo

El control de Apple a veces se equivoca. El 6 de junio de 2022, apps de React Native y Flutter recibieron ITMS-90338 por nombres públicos de WebKit, como `setNavigationDelegate:` o `isMainFrame`, incluso en actualizaciones casi sin cambios. Al día siguiente, un ingeniero de Apple escribió que el problema estaba resuelto del lado de la validación e invitó a reenviar los builds. El hilo se conserva en el [repositorio de GitHub de Expo](https://github.com/expo/expo/issues/17768).

Tres indicios juntos apuntan a un falso positivo:

- el build se parece al último build aceptado;
- el paso 2 no encuentra el nombre, o el paso 3 lo muestra como público en iOS;
- otros desarrolladores reportan el mismo error el mismo día en los foros de Apple.

En ese caso, revisa el [estado de los servicios de Apple](https://developer.apple.com/system-status/) y luego reenvía un build con un nuevo número. Si el error persiste, escribe al [soporte para desarrolladores de Apple](https://developer.apple.com/contact/) con el correo completo. Una colisión de nombre, en cambio, no es un falso positivo para Apple: renombra.

## Antes de tu próximo envío

Los demás errores ITMS también tienen su guía: [ITMS-90725](https://devoria.fr/es/blog/itms-90725-sdk-version-issue-expo) para la versión de Xcode, [ITMS-91053](https://devoria.fr/es/blog/itms-91053-missing-api-declaration-expo) para el manifiesto de privacidad e [ITMS-90034](https://devoria.fr/es/blog/itms-90034-missing-or-invalid-signature) para la firma. Una vez aceptado el build, instálalo con [TestFlight](https://devoria.fr/es/blog/testflight-probar-app-iphone-antes-de-enviarla) antes de la revisión.

Los rechazos de un revisor requieren más perspectiva: el procedimiento está en la página [app rechazada por Apple](https://devoria.fr/es/expertise/app-rechazada-por-apple), y los motivos más frecuentes están en [por qué Apple rechaza una app](https://devoria.fr/es/blog/rechazo-apple-por-que-rechazan-tu-app).

Para revisar tu proyecto antes del envío, el [Review Kit](https://devoria.fr/es/kits/app-review) reúne mi checklist y skills de Claude Code que revisan tu proyecto. Si tu envío se traba y quieres una mirada humana, [reserva una hora conmigo](https://devoria.fr/es/coaching): revisamos tu proyecto por videollamada, en tu pantalla. Y si estás preparando tu primer lanzamiento, [haz tu diagnóstico gratuito](https://devoria.fr/es/diagnostic): ordena los pasos y te envía mi checklist contra los rechazos de Apple.

## Preguntas frecuentes

### ¿ITMS-90338 es un rechazo de App Review?

No. Es un control automático que se hace durante el procesamiento del build, antes de cualquier revisión humana. El build rechazado no llega ni a TestFlight ni a revisión. Una API privada que un revisor detecte después corresponde, en cambio, a la guideline 2.5.1.

### ¿Se puede corregir ITMS-90338 con una actualización remota?

No. Los nombres citados están en el código nativo compilado del binario. Una actualización remota solo cambia el JavaScript y los recursos. Corrige la dependencia, haz un nuevo build y vuelve a enviarlo.

### ¿Por qué Apple cita métodos que nunca escribí?

Porque vienen de una biblioteca incluida, o de una colisión: una dependencia declara un método que lleva el nombre de una API privada. Apple lee los nombres presentes en el binario, no tu código fuente. Llega hasta el paquete con `grep` y `npm explain`.

### ¿Cómo saber si una API es pública en iOS?

Busca su nombre en los encabezados del SDK de iOS, cuya ruta te da `xcrun --sdk iphoneos --show-sdk-path`, y luego revisa su disponibilidad. La página de documentación de Apple también lo indica. Una función documentada solo para macOS no es pública en iPhone.

### ¿Hay que cambiar el número de build después de la corrección?

Sí. Deja que EAS lo incremente: un número ya enviado para la misma versión puede ser rechazado con ITMS-90189.


## Tu proyecto

[Review Kit](https://devoria.fr/es/kits/app-review): Mi checklist de revisión de App Store, mis rechazos de Apple corregidos, la de Google Play y tres skills de Claude Code. 49 €.

[iOS & Android Kit](https://devoria.fr/es/kits/ios): Crea tu app para iPhone y Android con Claude Code: el código base de mis apps, el camino hasta las tiendas y todos los kits. 99 €.

[Diagnóstico: lo que te separa de tu app para iPhone](https://devoria.fr/es/diagnostic): 7 preguntas, 60 segundos: descubre qué te frena de verdad para publicar tu app para iPhone, tu ruta en 4 etapas y el kit ideal para ti.
