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

![Imagem de capa de [Modularização #4] Modularização iOS com SPM](/assets/images/posts/2dfa8b93-209b-47ab-8ad8-2745ec0ab314/1.jpg)