No artigo anterior, vimos a última letra de SOLID, o DIP (Princípio da Inversão de Dependência). Depois de entender o princípio, surge naturalmente a próxima pergunta: “Então, quem monta essas dependências em um app real, e como?”
Em projetos pequenos, a DI manual (Dependency Injection, injeção de dependência) passando dependências diretamente pelo inicializador é suficiente. Porém, quando o número de telas chega às dezenas e o grafo de dependências se aprofunda, o próprio código de montagem começa a pesar. Hoje, vamos conhecer o Factory, uma biblioteca de DI para Swift adequada para esse cenário. O texto usa como referência a versão mais recente em julho de 2026: 3.3.1.
Até onde a DI manual consegue chegar?
O código que segue o DIP tinha aproximadamente esta aparência.
protocol NetworkProviding {
func fetch(_ url: URL) async throws -> Data
}
final class OrderViewModel {
private let network: NetworkProviding
init(network: NetworkProviding) {
self.network = network
}
}
O código depende de protocolos, e as implementações são injetadas de fora. Até aqui, nenhuma biblioteca é necessária. O problema está no lado que faz a montagem.
// um ponto de montagem em algum lugar(Composition Root)
let network = NetworkProvider()
let repository = OrderRepository(network: network)
let analytics = AnalyticsService(network: network)
let viewModel = OrderViewModel(repository: repository, analytics: analytics)
Quando as dependências chegam a três ou quatro níveis, esse código de inicialização se repete em cada tela. Se uma dependência é adicionada a uma camada intermediária, é preciso alterar todo código de inicialização que passa por ela. É quando cresce a tentação de fugir para um singleton, mas você já sabe como singletons atrapalham os testes. Um contêiner de DI assume justamente esse problema de montagem.
O diferencial do Factory: segurança em tempo de compilação
Por muito tempo, o Swinject foi usado quase como padrão entre as bibliotecas de DI para Swift. Ele registra por string ou tipo e recupera com resolve(); por isso, se um registro for esquecido, a compilação passa e nil aparece em tempo de execução. Você só descobre o erro ao executar o app.
O Factory inverte esse cenário. Como as dependências são definidas como propriedades computadas de Container, referenciar uma dependência inexistente simplesmente não compila. Erros de digitação e registros ausentes são detectados na etapa de build.
Além disso, é uma biblioteca leve, com menos de 1.000 linhas de código executável, que funciona apenas com Swift puro, sem script de geração de código em tempo de compilação. Não é preciso inserir ferramentas no pipeline de build como no Needle.
Uso básico do Factory em 3 minutos
A instalação é feita pelo SPM (Swift Package Manager). O endereço do pacote é https://github.com/hmlongco/Factory e, a partir da versão 3.x, o import é FactoryKit.
O registro de dependências é feito adicionando propriedades computadas a uma extensão de Container.
import FactoryKit
extension Container {
var networkService: Factory<NetworkProviding> {
self { NetworkProvider() }
}
var orderRepository: Factory<OrderRepositoryType> {
self { OrderRepository(network: self.networkService()) }
}
}
Há três formas de obtê-las.
// 1. Injeção por property wrapper
final class OrderViewModel {
@Injected(\.orderRepository) private var repository
}
// 2. Chamada direta
let repository = Container.shared.orderRepository()
// 3. Injeção pelo inicializador — deixar apenas a montagem com o contêiner
extension Container {
var orderViewModel: Factory<OrderViewModel> {
self { OrderViewModel(repository: self.orderRepository()) }
}
}
A terceira opção merece atenção. A própria classe continua usando uma forma pura de injeção pelo inicializador, sem conhecer a biblioteca, enquanto apenas o código de montagem fica a cargo do contêiner. É exatamente o princípio abordado no artigo sobre DIP: “a montagem das implementações é responsabilidade do lado externo”.
No SwiftUI, você pode receber diretamente um view model Observable usando @InjectedObservable.
struct OrderView: View {
@InjectedObservable(\.orderViewModel) var viewModel
}
Escopos: declare o ciclo de vida das instâncias
Outro motivo para usar um contêiner de DI é gerenciar o ciclo de vida das instâncias. No Factory, basta adicionar um modificador ao registro.
extension Container {
var networkService: Factory<NetworkProviding> {
self { NetworkProvider() }.singleton
}
var imageCache: Factory<ImageCaching> {
self { ImageCache() }.cached
}
}
- unique — Valor padrão. Cria uma nova instância a cada solicitação.
- singleton — Compartilha uma instância em todo o app.
- cached — Retorna a mesma instância até o cache ser resetado.
- shared — Mantém a instância apenas enquanto alguém tiver uma referência forte; quando ninguém a usa, ela é liberada.
A diferença em relação a criar diretamente um objeto singleton global é que o ciclo de vida fica declarado em um único registro, em vez de espalhado pelo código como static let. Se quiser mudar singleton para cached depois, basta trocar um modificador.
É nos testes e previews que ele mostra seu valor
O motivo mais prático para adotar DI é, no fim das contas, testar. O Factory permite sobrescrever um registro no próprio local.
import FactoryTesting
@Suite(.container) // Isolar o contêiner em cada teste
struct OrderViewModelTests {
@Test func loadsOrders() async {
Container.shared.orderRepository { MockOrderRepository() }
let viewModel = Container.shared.orderViewModel()
await viewModel.load()
#expect(viewModel.orders.count == 3)
}
}
No Swift Testing, adicionar a trait .container ao target FactoryTesting impede que o estado do contêiner se misture entre os testes. Nos previews do SwiftUI, você também pode inserir mocks da mesma forma.
#Preview {
Container.shared.orderRepository { MockOrderRepository() }
return OrderView()
}
Também existe um modificador de contexto que troca automaticamente a implementação apenas em um ambiente de execução específico. Se quiser usar analytics stub somente em builds de debug, faça assim.
container.analytics.onDebug { StubAnalyticsEngine() }
Pelo mesmo princípio, você pode declarar no registro overrides específicos para testes, previews e ambientes de simulador.
O que mudou no Factory 3.x
Para quem está migrando da versão 2.x, estes são os principais pontos.
- Mudança no nome do import —
import Factorymudou paraimport FactoryKit. A maior parte da migração é apenas essa substituição. - Suporte completo a Swift 6 Strict Concurrency — Ao registrar um view model
@MainActor, na versão 2.x era necessário repetir@MainActor indentro da closure; na 3.x, basta a anotação na declaração da factory. - Somente SPM — O suporte a CocoaPods foi descontinuado. Projetos CocoaPods precisam permanecer no Factory 2.5.3 ou incorporar o código-fonte diretamente.
- Suporte ao Swift Testing — O suporte oficial ao isolamento de testes, incluindo a trait
.containermostrada acima, foi adicionado.
Resumo
Se o DIP indica que devemos depender de abstrações, e não de implementações concretas, o Factory reduz o custo de manter essa direção em um código real. A segurança em tempo de compilação transforma registros ausentes em erros de build, enquanto escopos e substituições por mocks exigem apenas algumas declarações.
Não é necessário reescrever um projeto que já funciona bem com Swinject, mas em um projeto novo o Factory pode ser uma boa escolha padrão. Ele é leve e fácil de adotar; se não gostar, você pode remover apenas o contêiner e manter a estrutura de injeção pelo inicializador.
Se a DI manual começou a atrapalhar porque o código de montagem virou um obstáculo, experimente.

