ITMS-90338: corregir “Non-public API usage” con Expo
Por Mehdi Azizi
Publicado el , 9 min
ITMS-90338: tu build cita una API que Apple considera privada. Encuentra la dependencia, actualízala y vuelve a hacer un build.
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: una app solo puede usar API públicas. El contrato de licencia del Apple Developer Program 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. 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:
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:
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:
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.
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:
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:
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: 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, 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. Apple sí documenta esta función, pero solo para macOS, y el encabezado del SDK de iOS la marca como API_UNAVAILABLE(ios, tvos, watchos).
Corregir sin hacer trampa
- Actualiza la dependencia. Para los paquetes que sigue Expo,
npx expo install --checkseñala los que no corresponden a tu SDK, según la documentación de Expo CLI. Para los demás, lee el historial de versiones. - Vuelve a la versión anterior si fue la nueva la que introdujo el nombre, y avisa al proveedor.
- Reemplaza o quita una biblioteca abandonada.
- 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. 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.
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 y luego reenvía un build con un nuevo número. Si el error persiste, escribe al soporte para desarrolladores de Apple 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 para la versión de Xcode, ITMS-91053 para el manifiesto de privacidad e ITMS-90034 para la firma. Una vez aceptado el build, instálalo con TestFlight 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, y los motivos más frecuentes están en por qué Apple rechaza una app.
Para revisar tu proyecto antes del envío, el Review Kit 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: revisamos tu proyecto por videollamada, en tu pantalla. Y si estás preparando tu primer lanzamiento, haz tu diagnóstico gratuito: 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.

