# ITMS-90338: como corrigir “Non-public API usage” no Expo

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

Página original: [ITMS-90338: como corrigir “Non-public API usage” no Expo](https://devoria.fr/pt-br/blog/itms-90338-non-public-api-usage-expo)
Autor: [Mehdi Azizi](https://devoria.fr/pt-br/mehdi-azizi)
Publicação: 2026-11-04
Última revisão do conteúdo: 2026-11-04


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](https://developer.apple.com/app-store/review/guidelines/#2.5.1): um app só pode usar APIs públicas. O [contrato de licença do Apple Developer Program](https://developer.apple.com/support/terms/apple-developer-program-license-agreement/) 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](https://devoria.fr/pt-br/blog/guideline-2-5-1-apis-publicas-sdk-expo). 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:

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

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

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

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

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

```bash
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](https://github.com/talsec/Free-RASP-Flutter/issues/236): 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](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html), 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`](https://github.com/dotnet/runtime/issues/103949). A Apple documenta essa função, sim, mas [só para macOS](https://developer.apple.com/documentation/systemconfiguration/1437828-scdynamicstorecreate), e o header do SDK iOS a marca como `API_UNAVAILABLE(ios, tvos, watchos)`.

## Corrigir sem contornar

1. **Atualize a dependência.** Para os pacotes acompanhados pelo Expo, `npx expo install --check` aponta os que não batem com o seu SDK, segundo a [documentação do Expo CLI](https://docs.expo.dev/more/expo-cli/). Para os outros, leia o changelog.
2. **Volte para a versão anterior** se foi a nova que introduziu o nome, e avise o fornecedor.
3. **Substitua ou remova** uma biblioteca abandonada.
4. **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](https://devoria.fr/pt-br/blog/build-number-app-store-connect-versao-numero-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](https://github.com/expo/expo/issues/17768).

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](https://developer.apple.com/system-status/) e reenvie um build com um novo número. Se o erro continuar, escreva para o [suporte ao desenvolvedor da Apple](https://developer.apple.com/contact/) 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](https://devoria.fr/pt-br/blog/itms-90725-sdk-version-issue-expo) para a versão do Xcode, [ITMS-91053](https://devoria.fr/pt-br/blog/itms-91053-missing-api-declaration-expo) para o manifesto de privacidade e [ITMS-90034](https://devoria.fr/pt-br/blog/itms-90034-corrigir-missing-or-invalid-signature) para a assinatura. Depois que o build for aceito, instale-o pelo [TestFlight](https://devoria.fr/pt-br/blog/testflight-como-testar-app-iphone-antes-envio) 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](https://devoria.fr/pt-br/expertise/app-recusado-pela-apple), e os motivos mais comuns estão em [por que a Apple rejeita um app](https://devoria.fr/pt-br/blog/app-rejeitado-apple-casos-reais).

Para revisar seu projeto antes do envio, o [Review Kit](https://devoria.fr/pt-br/kits/app-review) 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](https://devoria.fr/pt-br/coaching): 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](https://devoria.fr/pt-br/diagnostic): 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.


## Seu projeto

[Review Kit](https://devoria.fr/pt-br/kits/app-review): Meu checklist de revisão da App Store, as recusas da Apple que corrigi, o checklist do Google Play e três skills do Claude Code. R$ 289.

[iOS & Android Kit](https://devoria.fr/pt-br/kits/ios): Crie seu app para iPhone e Android com o Claude Code: o código inicial dos meus apps, o caminho até as lojas e todos os kits. R$ 589.

[Diagnóstico: o que te separa do seu app de iPhone](https://devoria.fr/pt-br/diagnostic): 7 perguntas, 60 segundos: descubra o que realmente te impede de publicar seu app de iPhone, seu caminho em 4 etapas e o kit certo para você.
