Design de software

[Modularização #4] Modularização iOS com SPM

Na série anterior sobre modularização, resumimos os princípios de limites, direção das dependências e estrutura em camadas. Agora é hora de aplicá-los a um projeto iOS.

4 min de leitura
Imagem de capa de [Modularização #4] Modularização iOS com SPM

Na série anterior sobre modularização, resumimos os princípios de limites, direção das dependências e estrutura em camadas. Agora é hora de aplicá-los a um projeto iOS.

No iOS, há basicamente duas ferramentas para dividir módulos: targets de framework do Xcode e Swift Package (SPM, Swift Package Manager).

Em resumo, em 2026 o SPM é a escolha padrão para uma modularização nova. Os módulos podem ser criados apenas com pastas e Package.swift, sem conflitos no arquivo de projeto (pbxproj).

Este artigo resume como criar módulos locais com SPM, suas diferenças em relação aos targets de framework e como escolher entre linking estático e dinâmico.


Por que SPM em vez de targets de framework?

Também é possível criar um módulo no Xcode por File → New → Target → Framework. Durante muito tempo, essa foi a abordagem padrão.

O problema são as alterações deixadas por esse método. Cada target adicionado muda dezenas de linhas do project.pbxproj, e alterações simultâneas feitas por dois membros da equipe causam conflitos de merge.

Pacotes SPM locais são diferentes.

Comparação Target de framework Pacote SPM local
Local da definição do módulo project.pbxproj Package.swift (código declarativo)
Conflitos de merge Frequentes Raros (o arquivo contém código legível)
Custo para adicionar um módulo Operação na GUI do Xcode Pasta + algumas linhas
Configurações de build detalhadas Flexível Limitado

Quanto mais módulos existem, mais o custo de adicionar um deles define a arquitetura. Se adicionar módulos for assustador, ninguém vai dividir o sistema.

A menos que você precise controlar as configurações de build em detalhes, os pacotes SPM locais têm menor custo de manutenção.


Como criar um pacote SPM local?

Crie uma pasta ao lado do projeto do app e coloque nela o Package.swift. Vamos transportar diretamente a estrutura em camadas do artigo anterior.

// Modules/Package.swift
let package = Package(
    name: "Modules",
    products: [
        .library(name: "FeatureSearch", targets: ["FeatureSearch"]),
        .library(name: "CoreNetwork", targets: ["CoreNetwork"]),
    ],
    targets: [
        .target(name: "FeatureSearch", dependencies: ["CoreNetwork"]),
        .target(name: "CoreNetwork"),
        .testTarget(name: "FeatureSearchTests", dependencies: ["FeatureSearch"]),
    ]
)

Arraste este pacote para o projeto do Xcode, conecte a biblioteca ao target do app e import FeatureSearch funcionará.

O ponto principal é que as dependências são declaradas como código no array dependencies. Se FeatureSearch não deve usar CoreNetwork, remova-o do array; um import oculto gerará um erro de compilação.

É assim que são criados os “limites impostos pelo compilador” mencionados no artigo anterior.

O array dependencies se torna diretamente o grafo de dependências
O array dependencies se torna diretamente o grafo de dependências

Em vez de colocar vários targets em um pacote, também é possível usar um pacote por módulo. Com poucos módulos, um único pacote com vários targets é mais fácil de gerenciar; quando chegam a dezenas, muitas equipes dividem os pacotes por domínio.


static ou dynamic: qual escolher?

Ao declarar .library, você pode definir o modo de linking. O padrão é automatic, escolhido pelo Xcode e geralmente processado como static.

  • static: o código do módulo é copiado e incorporado ao binário do app. Não há carregamento adicional na inicialização.
  • dynamic: o módulo existe como um arquivo de framework separado e é carregado em tempo de execução.

Estes são os critérios de escolha.

Situação Escolha
Módulo comum de funcionalidade ou core static (manter o padrão)
App, widget e extensão compartilham o mesmo módulo dynamic (eliminar duplicação do binário)
O tempo de inicialização é crítico, mas há dezenas de módulos dynamic Considerar consolidar como static

Mais frameworks dynamic aumentam o custo de carregamento na inicialização. A Apple recomenda isso continuamente em sessões da WWDC; salvo um motivo específico, manter automatic (na prática, static) é uma escolha segura.


Quando o SPM sozinho deixa de ser suficiente?

Você pode começar com a modularização usando SPM e usá-la bem, mas equipes maiores acabam encontrando limitações.

  • As configurações do próprio target do app (assinatura, schemes e configurações de build) continuam no pbxproj, mantendo a possibilidade de conflitos.
  • Com dezenas de módulos, gerenciar o Package.swift e padronizar os templates de módulos se torna trabalhoso.
  • Você quer compartilhar o cache de build em toda a organização.

A ferramenta que aparece nesse ponto é o Tuist. Esse será o tema do último artigo da série.

É assim que isso aparece em entrevistas

P. Por que usar pacotes SPM locais na modularização de iOS?

As definições de módulos são gerenciadas como código declarativo no Package.swift, em vez de pbxproj, reduzindo conflitos de merge e o custo de adicionar módulos. Além disso, as dependências entre targets são explicitadas em dependencies, então imports não permitidos são bloqueados com erros de compilação e o compilador impõe os limites dos módulos.

P. Explique a diferença entre bibliotecas static e frameworks dynamic e os critérios para escolher cada um.

static é copiado para o binário do app no linking, sem custo de carregamento em tempo de execução; dynamic existe como um arquivo separado e é carregado em runtime. Use static por padrão, mas escolha dynamic quando app, extensões e widgets compartilharem código e for necessário reduzir a duplicação do binário.

Há uma satisfação discreta em ver as pastas de pacotes crescerem uma a uma
Há uma satisfação discreta em ver as pastas de pacotes crescerem uma a uma

O próximo artigo aborda o verdadeiro motivo pelo qual muitas equipes começam a modularizar: a velocidade de build do Xcode.

Vamos abordar por que os builds ficam lentos, onde e quanto a modularização melhora o processo e como medir numericamente esse efeito.