ITMS-90338: como corrigir “Non-public API usage” no Expo
Por Mehdi Azizi
Publicado em , 9 min
ITMS-90338: seu build cita um método, função ou classe que a Apple considera privada. Ache a dependência culpada, atualize e gere um novo build.
ITMS-90338 é uma verificação automática da Apple no seu build. Ela diz que o seu binário faz referência a APIs que a Apple considera privadas: métodos Objective-C, funções ou classes. A correção: localizar o arquivo citado no e-mail, rastrear até a dependência que contém o nome, atualizá-la ou renomear o seu método, e depois gerar um novo build.
Com Expo, o nome quase sempre vem de uma biblioteca nativa, raramente do seu JavaScript. Em 15 de outubro de 2026, rodei os comandos de análise deste guia num build de simulador do Solys, meu despertador com minijogos (SDK Expo 57), e no SDK iOS do Xcode 26.2. Veja como ler o e-mail, encontrar o culpado e reconhecer um falso positivo.
O que significa o erro ITMS-90338?
A mensagem se chama "ITMS-90338: Non-public API usage". Ela tem três variantes, dependendo do que a Apple encontrou no seu binário:
- "references non-public selectors": nomes de métodos Objective-C, como
boldFontOfSize:; - "references non-public symbols": funções, escritas com um underline na frente, como
__dyld_get_shared_cache_range; - "contains or inherits from non-public classes": um nome de classe.
Cada variante traz um caminho e depois a lista de nomes. O texto acrescenta duas dicas: renomear seus métodos se o nome coincidir com uma API privada, e remover a biblioteca estática que os contém. Um e-mail de setembro de 2026 também sugere xcrun swift-demangle para deixar legíveis os nomes Swift codificados.
A regra por trás dessa verificação é a guideline 2.5.1: um app só pode usar APIs públicas. O contrato de licença do Apple Developer Program repete isso no item 3.3.1: nenhuma chamada a API privada. A revisão humana dessa regra está detalhada no guia da guideline 2.5.1. Aqui, a gente trata o erro de envio.
Como nos outros erros ITMS, ninguém abriu o seu app: a Apple analisou o binário durante o processamento do build. A Apple não publica nenhuma lista de APIs privadas: os nomes do seu e-mail são a única lista confiável.
Ler o caminho: um SDK ou o seu executável?
O caminho escrito antes da lista diz em qual arquivo procurar.
Frameworks/Nom.framework/Nom: o nome está num framework dinâmico embutido, ou seja, num SDK específico. No build do Solys, essa pasta tem 14 frameworks: Hermes, React Native, módulos Expo entregues já compilados e a biblioteca de imagens SDWebImage.- Só o nome do seu app: o nome está no executável principal. Com Expo, ele reúne o seu código nativo e os pods compilados a partir do código-fonte, linkados estaticamente por padrão. Qualquer biblioteca nativa pode ser a culpada.
A árvore: do nome citado à correção
Siga as linhas em ordem. A primeira que bate com o seu caso dá a pista.
| O que você lê | Onde procurar | Correção |
|---|---|---|
Nome em $s ou _$s | xcrun swift-demangle | Recomeçar a árvore |
| Não encontrado no seu IPA | grep no bundle | Reenviar um build |
| Nome público no iOS | Headers do SDK iOS | Reenviar um build |
API_UNAVAILABLE(ios) | Headers do SDK iOS | Atualizar o pacote |
Em Frameworks/X | O SDK que fornece X | Atualizar o SDK |
| Seletor, seu app | Pods e módulos nativos | Prefixar ou atualizar |
Símbolo _nome, seu app | nm -m, depois os pods | Substituir a chamada |
| Classe, seu app | Suas classes e seus pods | Prefixar a classe |
As linhas "Reenviar um build" indicam um falso positivo, tratado mais abaixo.
Etapa 1: abrir o build enviado
Trabalhe no arquivo realmente enviado à Apple, não no seu projeto. Baixe o IPA na página do build no expo.dev e rode no seu Mac:
unzip -q MyApp.ipa -d ipa
ls ipa/Payload/MyApp.app/Frameworks
otool -L ipa/Payload/MyApp.app/MyApp | grep PrivateFrameworks
A segunda linha lista os frameworks embutidos. A terceira procura um link direto para um framework privado da Apple: ela não deve mostrar nada. No Solys, o otool -L não encontra nenhum framework privado, nem no executável nem nos 14 frameworks.
Etapa 2: encontrar o arquivo que contém o nome
Comece amplo. Procure o nome citado, sem os underlines do começo, no bundle inteiro:
grep -rl "dyld_get_shared_cache_range" ipa/Payload/MyApp.app
O comando lista os arquivos que contêm esse texto. Depois refine conforme o tipo de nome (seletor, símbolo, classe), no arquivo encontrado, chamado aqui de Binaire:
strings -a Binaire | grep -x "boldFontOfSize:"
nm -m Binaire | grep "_dyld_get_shared_cache_range"
nm -m Binaire | grep '_OBJC_CLASS_$_NomDeClasse'
Para um seletor, use strings, não nm. No framework React do Solys, o nm mostra métodos com a classe, como -[RCTRootView initWithBridge:moduleName:initialProperties:], nunca o seletor initWithBridge: sozinho. O strings encontra.
Para um símbolo, leia o fim da linha. No Solys, o nm -m mostra por exemplo (undefined) external __dyld_get_image_name (from libSystem): o binário chama a função sem contê-la, e a libSystem a fornece. Essa função é pública, como mostra a próxima etapa.
Etapa 3: verificar se o nome é mesmo privado
Os headers do SDK iOS descrevem as APIs públicas. Uma busca de poucos segundos diz se o nome citado aparece neles, e em quais plataformas. Para um símbolo, tire antes o underline adicionado na compilação: _SCDynamicStoreCreate vira SCDynamicStoreCreate.
SDK=$(xcrun --sdk iphoneos --show-sdk-path)
grep -rn "SCDynamicStoreCreate" "$SDK/usr/include" "$SDK"/System/Library/Frameworks/*/Headers
Em 15 de outubro de 2026, com o SDK iOS 26.2, este é o resultado para quatro nomes tirados de e-mails ITMS-90338 reais:
| Nome citado | No SDK iOS | Leitura |
|---|---|---|
| _dyld_get_shared_cache_range | Ausente | API privada |
| boldFontOfSize: | Ausente | Privada ou colisão |
| SCDynamicStoreCreate | API_UNAVAILABLE(ios) | Só macOS |
| isMainFrame | Propriedade do WebKit | Pública |
Um nome presente e disponível no iOS aponta para um falso positivo. Um nome ausente aponta para uma API privada ou uma colisão. Dois limites: uma API pública só em Swift não aparece nesses headers, e para um acessor como setDelegate:, procure a propriedade delegate.
Etapa 4: rastrear até o pacote
Se o caminho cita um framework, o nome dele leva ao pod e depois ao pacote npm:
grep -n "NomDuFramework" ios/Podfile.lock
npm explain nom-du-paquet
O npm explain diz qual dependência o instalou, se você nunca o adicionou por conta própria.
Se o caminho cita o executável, procure o nome nos fontes nativos e depois nos pods:
grep -rl --include='*.m' --include='*.mm' --include='*.swift' --include='*.h' \
"boldFontOfSize" node_modules modules
grep -rl "boldFontOfSize" ios/Pods
Adapte a lista de pastas: modules é a dos módulos Expo locais. A pasta ios/Pods aparece depois de npx expo prebuild -p ios, que também instala os pods no Mac. Cuidado: esse comando pode recriar a pasta ios. Se você a modificou na mão, trabalhe numa cópia.
A busca em ios/Pods conta tanto quanto a dos fontes. No Solys, essa pasta tem 15 frameworks entregues já compilados, no formato .xcframework: módulos Expo, Hermes, React Native e SDKs de terceiros. Uma busca limitada aos arquivos-fonte deixaria o código deles de fora.
As causas típicas
Um SDK de terceiros, antigo demais ou novo demais. Em setembro de 2026, um desenvolvedor Flutter relatou uma rejeição causada por um SDK de segurança: a versão 8.2.3 chamava __dyld_get_shared_cache_range. A versão 8.2.2 passava, e o fornecedor recomendou voltar para ela enquanto não saía uma correção.
Um módulo nativo que chama uma API não documentada. Seu próprio módulo, um plugin ou uma biblioteca pequena pode usar um método interno do UIKit achado num fórum. A verificação o detecta pelo nome.
Uma colisão de nome. Seu método ou sua classe tem, por acaso, o nome de uma API privada da Apple. A mensagem prevê isso e pede que você renomeie. A Apple recomenda prefixar os métodos adicionados às classes dela, por exemplo com três letras minúsculas seguidas de um underline.
Uma API pública no macOS, mas não no iOS. Em 2024, um app .NET MAUI foi rejeitado por _SCDynamicStoreCreate. A Apple documenta essa função, sim, mas só para macOS, e o header do SDK iOS a marca como API_UNAVAILABLE(ios, tvos, watchos).
Corrigir sem contornar
- Atualize a dependência. Para os pacotes acompanhados pelo Expo,
npx expo install --checkaponta os que não batem com o seu SDK, segundo a documentação do Expo CLI. Para os outros, leia o changelog. - Volte para a versão anterior se foi a nova que introduziu o nome, e avise o fornecedor.
- Substitua ou remova uma biblioteca abandonada.
- Renomeie seu método ou sua classe, com um prefixo.
Não esconda o nome. Quebrar uma string ou chamar o método por um caminho indireto esconde o uso sem corrigi-lo. Ele continua proibido pela regra 2.5.1 e pelo contrato, e aí quem fica exposta é a sua conta.
Depois, gere um novo build com um novo número de build. Uma atualização remota não basta: o nome está no código nativo compilado. Antes do envio, rode de novo a etapa 2 no novo IPA. O nome não pode mais aparecer.
Quando é um falso positivo
A verificação da Apple às vezes erra. Em 6 de junho de 2022, apps React Native e Flutter receberam ITMS-90338 por nomes públicos do WebKit, como setNavigationDelegate: ou isMainFrame, inclusive em atualizações quase sem mudanças. No dia seguinte, um engenheiro da Apple escreveu que o problema tinha sido resolvido no lado da validação e pediu para reenviar os builds. A thread está guardada no repositório GitHub do Expo.
Três sinais juntos apontam para um falso positivo:
- o build é parecido com o último build aceito;
- a etapa 2 não encontra o nome, ou a etapa 3 mostra que ele é público no iOS;
- outros desenvolvedores relatam o mesmo erro no mesmo dia nos fóruns da Apple.
Nesse caso, confira o status dos serviços da Apple e reenvie um build com um novo número. Se o erro continuar, escreva para o suporte ao desenvolvedor da Apple com o e-mail completo. Já uma colisão de nome não é um falso positivo para a Apple: renomeie.
Antes do seu próximo envio
Os outros erros ITMS também têm guia: ITMS-90725 para a versão do Xcode, ITMS-91053 para o manifesto de privacidade e ITMS-90034 para a assinatura. Depois que o build for aceito, instale-o pelo TestFlight antes da revisão.
As rejeições de um reviewer pedem mais calma: o passo a passo está na página app rejeitado pela Apple, e os motivos mais comuns estão em por que a Apple rejeita um app.
Para revisar seu projeto antes do envio, o Review Kit reúne meu checklist e skills do Claude Code que revisam o seu projeto. Se o seu envio travou e você quer um olhar humano, agende uma hora comigo: a gente revisa seu projeto por videochamada, na sua tela. E se você está preparando seu primeiro lançamento, faça seu diagnóstico gratuito: ele coloca as etapas em ordem e te envia meu checklist anti-rejeição da Apple.
Perguntas frequentes
ITMS-90338 é uma rejeição do App Review?
Não. É uma verificação automática feita durante o processamento do build, antes de qualquer revisão humana. O build recusado não chega ao TestFlight nem à revisão. Já uma API privada detectada depois por um reviewer entra na guideline 2.5.1.
Dá para corrigir o ITMS-90338 com uma atualização remota?
Não. Os nomes citados estão no código nativo compilado do binário. Uma atualização remota só muda o JavaScript e os recursos. Corrija a dependência, gere um novo build e reenvie.
Por que a Apple cita métodos que eu nunca escrevi?
Porque eles vêm de uma biblioteca embutida, ou de uma colisão: uma dependência declara um método com o nome de uma API privada. A Apple lê os nomes presentes no binário, não o seu código-fonte. Rastreie até o pacote com grep e npm explain.
Como saber se uma API é pública no iOS?
Procure o nome nos headers do SDK iOS, cujo caminho é dado por xcrun --sdk iphoneos --show-sdk-path, e veja a disponibilidade. A página de documentação da Apple também informa isso. Uma função documentada só para macOS não é pública no iPhone.
Preciso mudar o número de build depois da correção?
Sim. Deixe o EAS incrementar: um número já enviado para a mesma versão pode ser recusado com ITMS-90189.

