Design de software

[Modularização #5] Por que os builds do Xcode são lentos e como resolver

Todo desenvolvedor iOS já esperou vários minutos depois de alterar uma linha de código e clicar em Build.

5 min de leitura
Imagem de capa de [Modularização #5] Por que os builds do Xcode são lentos e como resolver

Todo desenvolvedor iOS já esperou vários minutos depois de alterar uma linha de código e clicar em Build.

Se você faz dezenas de builds por dia, reduzir um minuto por build devolve mais de 30 minutos por dia, sem exagero.

A resposta principal: a maior causa estrutural da lentidão dos builds do Xcode é recompilar um escopo amplo demais mesmo após pequenas mudanças; a modularização reduz exatamente esse escopo.

Este artigo divide as causas por camada, mostra onde a modularização ajuda e explica como medir numericamente o antes e o depois.

Este é o resumo dos pontos principais.

  1. O escopo de recompilação de um build incremental fica restrito ao módulo. Sem módulos, o app inteiro é uma única unidade.
  2. Não faça tuning sem medir. O Build With Timing Summary do Xcode é a ferramenta básica.
  3. Também é preciso verificar causas locais, como gargalos de inferência de tipos e configurações de dSYM.

Por que os builds do Xcode ficam lentos?

As causas podem ser divididas em três camadas.

Primeiro, o escopo de recompilação.

Quando um arquivo Swift muda, o Swift recompila os arquivos afetados dentro do módulo ao qual ele pertence. Em um app não modularizado, porém, todo o target é um único módulo.

Em um target único com centenas de milhares de linhas, até uma alteração pequena causa uma recompilação ampla. Ao mexer em um tipo usado em vários lugares, o resultado fica praticamente próximo de um clean build.

Segundo, gargalos de verificação de tipos.

O compilador do Swift gasta bastante tempo com inferência de tipos. Uma expressão complexa pode levar vários segundos. Encadeamentos longos, ternários complexos e bodies enormes do SwiftUI são culpados frequentes.

Terceiro, configurações de build.

Se um build de debug gera dSYM (DWARF with dSYM File) ou ativa Whole Module Optimization de forma incorreta, cada build recebe um custo desnecessário.


Como a modularização melhora a velocidade dos builds?

A modularização melhora a primeira camada: o escopo de recompilação.

As fronteiras entre módulos funcionam como firewalls de recompilação. Se apenas a implementação interna de um módulo muda, o código fora dele não precisa ser recompilado.

A estrutura em camadas do artigo anterior mostra sua força aqui.

  • Ao alterar o interior do recurso de busca (FeatureSearch), a recompilação fica limitada ao FeatureSearch e aproximadamente ao target do app.
  • Por outro lado, ao mudar a interface pública de um módulo inferior do qual todos dependem (CoreNetwork), os módulos superiores são recompilados em cadeia.

Assim, os princípios de design de módulos voltados à velocidade de build surgem naturalmente.

  1. Coloque o código que muda com frequência a montante no grafo (Feature) e o código estável a jusante (Core·Domain).
  2. Mantenha mínima a interface public dos módulos a jusante (se não for public, não há impacto externo).
  3. Não crie um módulo Common inchado do qual todos dependam.

Há também um ganho de paralelismo. O Xcode compila simultaneamente módulos sem dependências, então um grafo mais largo e raso acelera também os clean builds.

Com fronteiras de módulo, a recompilação termina dentro deste escopo
Com fronteiras de módulo, a recompilação termina dentro deste escopo

Como medir o tempo de build?

Otimização baseada em intuição geralmente erra o alvo. Três ferramentas são suficientes.

Build With Timing Summary. Execute Product → Perform Action → Build With Timing Summary no Xcode para ver o tempo de cada etapa no fim do log. Comece identificando aqui o target e a etapa que formam o gargalo.

Flags de aviso de verificação de tipos. O compilador informa diretamente as funções e expressões lentas. Adicione-os a Other Swift Flags.

-Xfrontend -warn-long-function-bodies=100
-Xfrontend -warn-long-expression-type-checking=100
// 100ms Emitir aviso para funções e expressões que excederem o limite

Linha do tempo do build. Abra a linha do tempo na área Assistant do log para ver a execução paralela por target. Trechos longos em série indicam que o grafo de dependências está bloqueando o paralelismo.


O que verificar além da modularização

Ao medir, muitas vezes você descobre que o problema é uma linha de configuração, não a modularização. Este é o checklist para builds de debug.

Item Configuração recomendada (Debug)
Debug Information Format DWARF (desativar geração de dSYM)
Compilation Mode Incremental
Optimization Level -Onone
Build Active Architecture Only Yes

A partir do Xcode 16, Explicitly Built Modules também melhorou o paralelismo na etapa de preparação dos módulos. Usar o Xcode mais recente por si só pode melhorar a velocidade dos builds.

É assim que isso aparece em entrevistas

Q. Explique sua abordagem para reduzir o tempo de build em um app iOS de grande escala.

Primeiro, meça os gargalos com Build With Timing Summary e verifique as configurações (geração de dSYM no debug e modo de compilação). Estruturalmente, o essencial é usar modularização para confinar a recompilação ao módulo, colocar o código que muda com frequência a montante do grafo de dependências e minimizar a superfície public dos módulos a jusante.

Q. Se o build continuar lento mesmo depois de dividir os módulos, o que você suspeitaria?

Verifique se um módulo inferior compartilhado muda com frequência e se sua interface public é alterada muitas vezes. Encontre gargalos de verificação de tipos com flags de aviso, divida expressões complexas e use a linha do tempo para conferir se um grafo serializado está impedindo a compilação paralela.

O tempo esperando os builds se acumula mais do que parece ao longo de um dia
O tempo esperando os builds se acumula mais do que parece ao longo de um dia

A velocidade de build é o benefício mais perceptível da modularização, mas quando os módulos chegam às dezenas, o gerenciamento do projeto vira trabalho.

No último artigo da série, vou abordar o Tuist, uma ferramenta que reduz esse custo de gerenciamento. Também vou resumir como se livrar de conflitos no pbxproj e usar cache de binários.