Em um grande projeto, encontramos uma velocidade de construção baixa - de três minutos ou mais. Normalmente, nesses casos, os estúdios praticam projetos de modularização para não trabalhar com monólitos enormes. Nós da Surf decidimos experimentar e modularizar o projeto usando o Swift Package Manager - o gerenciador de dependências da Apple.
Falaremos dos resultados em outro artigo, e agora responderemos às principais perguntas: por que tudo isso é necessário, por que escolhemos o SPM e como demos os primeiros passos.

Por que SPM
A resposta é simples - é nativa e nova. Ele não cria overheads do xcworkspace, como o Cocoapods, por exemplo. Além disso, o SPM é um projeto de código aberto em desenvolvimento ativo. A Apple e a comunidade estão corrigindo bugs, corrigindo vulnerabilidades, atualizando para seguir o Swift.
Isso torna a construção do projeto mais rápida
Teoricamente, a montagem será acelerada pelo próprio fato de dividir a aplicação em módulos - frameworks. Isso significa que cada módulo só será criado quando alterações forem feitas nele. Mas será possível afirmar com certeza apenas no final do experimento.
Nota: A eficácia da modularização depende diretamente da divisão correta do projeto em módulos.
Como fazer um particionamento eficiente
O método de divisão depende da arquitetura escolhida, do tipo de aplicativo, de seu tamanho e dos planos de desenvolvimento posterior. Portanto, falarei sobre três regras que tentamos seguir quando nos separamos.
Compartilhe funcionalidades genéricas. Cada módulo é responsável por uma categoria, por exemplo:
- CommonAssets - um conjunto de seus ativos e uma interface pública para acessá-los. Geralmente é gerado usando SwiftGen.
- CommonExtensions - um conjunto de extensões, por exemplo Foundation, UIKit, dependências adicionais.
Fluxos de aplicativos separados. Considere uma estrutura de árvore, onde MainFlow é o fluxo principal do aplicativo. Digamos que temos um aplicativo de notícias.
- NewFlow - telas de notícias e visão geral de notícias específicas.
- FavoritesFlow — .
- SettingsFlow — , , . .
reusable :
- CommonUIComponents — , UI-. .
- UI . , , , . .
Digamos que temos um fluxo específico e queremos adicionar novas funções a ele. Se o componente for reutilizado ou se for teoricamente possível no futuro, é melhor movê-lo para um módulo separado ou um análogo de CommonUIComponents. Em outros casos, você pode deixar o componente local.
Essa abordagem resolve o problema de componentes ausentes. Isso acontece em grandes projetos e, se o componente não for documentado, sua manutenção e depuração se tornarão subseqüentemente não lucrativas.
Crie um projeto usando SPM
Considere a criação de um projeto de teste trivial. Estou usando o projeto Multiplatform App no SwiftUI. A plataforma e a interface são irrelevantes aqui.
Nota: Para criar rapidamente um aplicativo multiplataforma, você precisa do XCode 12.2 beta. Vamos criar um
projeto e ver o seguinte:

Agora vamos criar o primeiro módulo Comum:
- adicione a pasta Frameworks sem criar um diretório;
- crie um pacote SPM comum.

- Adicione as plataformas suportadas ao arquivo Package.swift. Nós temos isso
platforms: [.iOS(.v14), .macOS(.v10_15)]

- Agora adicionamos nosso módulo a cada destino. Temos este SPMExampleProject para iOS e SPMExampleProject para macOS.

Nota: É suficiente conectar apenas módulos raiz aos destinos. Eles não são adicionados como submódulos.
A conexão está completa. Agora tudo que você precisa fazer é configurar um módulo com uma interface pública - e voila, o primeiro módulo está pronto.
Como adicionar uma dependência para um pacote SPM local
Vamos adicionar o pacote AdditionalInfo - como Common, mas sem adicioná-lo aos destinos. Agora vamos mudar o Package.swift do pacote Common.

Você não precisa adicionar mais nada. Pode ser usado.
Um exemplo próximo da realidade
Vamos conectar o SwiftGen ao nosso projeto de teste e adicionar o módulo Paleta - ele será responsável por acessar a paleta de cores aprovada pelo designer.
- Crie um novo módulo raiz seguindo as instruções acima.
- Adicione os diretórios raiz de scripts e modelos a ele.
- Adicione o arquivo Palette.xcassets à raiz do módulo e anote quaisquer conjuntos de cores.
- Adicione um arquivo Palette.swift vazio a Sources / Palette.
- Adicione o modelo palette.stencil à pasta Modelos .
- Agora você precisa registrar o arquivo de configuração do SwiftGen. Para fazer isso, adicione o arquivo swiftgen.yml à pasta Scripts e escreva o seguinte nela:
xcassets:
inputs:
- ${SRCROOT}/Palette/Sources/Palette/Palette.xcassets
outputs:
- templatePath: ${SRCROOT}/Palette/Templates/palette.stencil
params:
bundle: .module
publicAccess: true
output: ${SRCROOT}/Palette/Sources/Palette/Palette.swift

A aparência final do
módulo Paleta Configuramos o módulo Paleta . Agora você precisa configurar o lançamento do SwiftGen para que a paleta seja gerada no início da construção. Para fazer isso, vá para a configuração de cada destino e crie uma nova fase de construção - vamos chamá-la de gerador de paleta. Não se esqueça de mover esta fase de construção para a posição mais alta possível.
Agora escrevemos a chamada para o SwiftGen:
cd ${SRCROOT}/Palette
/usr/bin/xcrun --sdk macosx swift run -c release swiftgen config run --config ./Scripts/swiftgen.yml
Nota:
/usr/bin/xcrun --sdk macosx
é um prefixo muito importante. Sem ele, a construção irá gerar um erro: "não foi possível carregar a biblioteca padrão para o destino 'x86_64-apple-macosx10.15".

Exemplo de chamada para SwiftGen
Done - as cores podem ser acessadas assim:
Palette.myGreen
(tipo de cor em SwiftUI) e PaletteCore.myGreen
(UIColor / NSColor).
Rochas subaquáticas
Vou listar o que encontramos.
- .
- SwiftLint & SwiftGen SPM. yml.
- Cocoapods. — , SPM . SPM Cocoapods - : «MergeSwiftModule failed with a nonzero exit code». , .
- SPM .
-L$(BUILD_DIR)
.
SPM — Bundler?
Nesse sentido, proponho sonhar e discutir nos comentários. O tema precisa ser bem estudado, mas parece muito interessante. A propósito, já existe um artigo bastante interessante sobre os prós e os contras do SPM.
O SPM oferece a capacidade de chamar o swift run adicionando Package.swift à raiz do projeto. O que isso nos dá? Por exemplo, você pode chamar fastlane ou swiftlint. Exemplo de chamada:
swift run swiftlint --autocorrect.