Design de software

Resumo da biblioteca de DI Swift Factory: qual é a diferença para o Swinject?

Factory é uma biblioteca de DI para Swift que detecta dependências não registradas em tempo de compilação. Com base na versão 3.3.1 de julho de 2026, este guia resume o uso básico, as declarações de escopo, a injeção em testes e previews e as diferenças em relação ao Swinject.

5 min de leitura
Imagem de capa de Resumo da biblioteca de DI Swift Factory: qual é a diferença para o Swinject?

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
}
Ilustração de um contêiner montando módulos de dependência e entregando-os às telas do app por uma esteira
Basta declarar no registro; a montagem fica por conta do contêiner

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.

Ilustração de injeção em testes substituindo o módulo real de um slot de máquina por um módulo mock
Nos testes, usamos mocks no lugar da implementação real

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 importimport Factory mudou para import 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 in dentro 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 .container mostrada acima, foi adicionado.
Diagrama da arquitetura do Factory com o registro do Container compartilhado pelo código do app, testes e previews
O código do app, os testes e os previews usam o mesmo registro

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.


Referências

Continue lendo