Dependency Injection: o que é, injeção de dependências em iOS e Android

Autor: IT Sectr Publicado: 2026-02-18 Tempo de leitura: 9 min

Injeção de Dependência (DI) é uma técnica na qual um objeto recebe suas dependências externamente, em vez de criá-las por si só. DI é uma implementação do princípio IoC (Inversão de Controle) e está na base do Dagger, Hilt e Swinject. A injeção de dependências reduz o acoplamento do código, simplifica os testes e torna a arquitetura flexível. No Android, DI é padrão através do Dagger Hilt do Google; no iOS, através do Swinject ou injeção manual. Saiba mais no Guia de DI do Android.

Principais pontos

  • Injeção de Dependência — as dependências são passadas ao objeto externamente, não criadas internamente
  • Inversão de Controle — DI implementa o princípio IoC, o fluxo de controle é transferido ao contêiner
  • Dagger Hilt — o padrão DI para Android, baseado no Dagger do Google
  • Swinject — um framework DI popular para iOS e Swift
  • Acoplamento reduzido — uma classe depende de abstrações, não de implementações concretas

O que é Dependency Injection: essência e tipos de DI

Injeção de Dependência é uma técnica na qual um objeto recebe dependências (serviços, repositórios, configurações) através de um construtor, setter ou interface, em vez de criá-las ele mesmo com new. O objetivo do DI é reduzir o acoplamento entre classes. Se uma classe cria dependências por si mesma, ela fica fortemente ligada a implementações específicas, dificultando testes e modificações. Com DI, a classe trabalha com uma abstração (protocolo/interface), e a implementação concreta é fornecida externamente.

Três formas de injeção — Injeção por Construtor (via init/constructor), Injeção por Setter (via propriedade/setter), Injeção por Interface (via um método de interface). A Injeção por Construtor é a abordagem preferida: as dependências são claramente visíveis na assinatura e o objeto é sempre criado em um estado válido. A Injeção por Setter é usada para dependências opcionais com valor padrão. A Injeção por Interface é rara, principalmente para contêineres DI.

Tipo de DIMétodoQuando usarExemplo
ConstrutorParâmetros do inicializadorDependências obrigatóriasinit(service: ServiceProtocol)
PropriedadePropriedade da classeDependências opcionaisvar service: ServiceProtocol?
MétodoParâmetro do métodoDependências temporáriasfunc doWork(with service: Service)

Contêiner DI — uma biblioteca que gerencia a criação e o ciclo de vida das dependências. O contêiner contém registros de tipos (cada tipo abstrato mapeado para uma implementação concreta) e uma fábrica para criar objetos com dependências resolvidas. No Android — Dagger/Hilt, no iOS — Swinject, Needle, Dip. O contêiner pode gerenciar escopo: singleton (uma instância por aplicação), escopo de funcionalidade (por tela) ou um novo objeto a cada solicitação.

Dagger Hilt: DI para Android com geração de código

Dagger Hilt é um invólucro sobre o Dagger do Google, a biblioteca DI padrão para Android. Hilt simplifica o Dagger: elimina a criação manual de componentes, adiciona @HiltAndroidApp, @AndroidEntryPoint e @Module. Hilt integra-se ao ciclo de vida do Android: ViewModel, Activity, Fragment, Service, BroadcastReceiver podem receber dependências através de anotações. A geração de código ocorre em tempo de compilação — o Dagger gera implementações de componentes, proporcionando sobrecarga de runtime zero.

kotlin
// Classe de aplicação
@HiltAndroidApp
class MyApp : Application()

// Módulo — define como criar dependências
@Module
@InstallIn(SingletonComponent::class)
object NetworkModule {
    @Provides
    @Singleton
    fun provideOkHttpClient(): OkHttpClient = OkHttpClient.Builder().build()

    @Provides
    @Singleton
    fun provideApiService(client: OkHttpClient): ApiService {
        return Retrofit.Builder()
            .baseUrl("https://api.example.com")
            .client(client)
            .build()
            .create(ApiService::class.java)
    }
}

// ViewModel recebe dependência através do construtor
@HiltViewModel
class MainViewModel @Inject constructor(
    private val apiService: ApiService
) : ViewModel() {
    private val _state = MutableStateFlow(MainState.Loading)
    val state: StateFlow<MainState> = _state.asStateFlow()

    fun loadData() {
        viewModelScope.launch {
            _state.value = MainState.Success(apiService.getData())
        }
    }
}

// Activity — @AndroidEntryPoint habilita DI
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()
}

Componentes e escopos do Dagger — @Singleton (toda a aplicação), @ActivityScoped (por Activity), @FragmentScoped (por Fragment), @ViewModelScoped (por ViewModel). A escolha do escopo determina o tempo de vida do objeto. @Singleton — uma instância por processo, adequado para OkHttpClient e bancos de dados. @ActivityScoped — o objeto vive enquanto a Activity vive, para dependências de tela. @ViewModelScoped — novo no Hilt 2.45+, o objeto vive enquanto o ViewModel vive, conveniente para escopos de corrotinas.

Swinject: DI para iOS em Swift

Swinject é um framework DI popular de código aberto para iOS. Swinject fornece Container, Assemblies e vários escopos. Ao contrário do Dagger, Swinject funciona em tempo de execução — as dependências são resolvidas dinamicamente sem geração de código. Isso torna o Swinject mais fácil de configurar, mas a depuração é mais difícil: um erro de dependência não resolvida só aparece em tempo de execução. Swinject suporta Injeção por Construtor, Injeção por Propriedade e Injeção por Método.

swift
import Swinject

// Assembly — um grupo de registros
class NetworkAssembly: Assembly {
    func assemble(container: Container) {
        container.register(NetworkServiceProtocol.self) { _ in
            NetworkService()
        }.inObjectScope(.container) // singleton

        container.register(UserRepositoryProtocol.self) { r in
            UserRepository(
                networkService: r.resolve(NetworkServiceProtocol.self)!
            )
        }
    }
}

// ViewModel via Injeção por Construtor
class ProfileViewModel: ObservableObject {
    private let repository: UserRepositoryProtocol

    init(repository: UserRepositoryProtocol) {
        self.repository = repository
    }

    @Published var user: User?
    func loadUser() {
        repository.fetchUser { [weak self] user in
            self?.user = user
        }
    }
}

// Configuração de DI no AppDelegate ou App
let assembler = Assembler([NetworkAssembly(), ServiceAssembly()])
let viewModel = assembler.resolver.resolve(ProfileViewModel.self)!

// Injeção por Propriedade para UIKit ViewController
container.register(UserViewController.self) { r in
    let vc = UserViewController()
    vc.viewModel = r.resolve(ProfileViewModel.self)
    return vc
}

Escopos do Swinject — .transient (novo objeto a cada vez), .container (singleton por contêiner), .graph (padrão — o objeto é compartilhado dentro de um único grafo de dependências). Para aplicações iOS, .container e .transient são suficientes. Swinject também suporta Assembler — agrupamento de Assembly para arquitetura modular. Para testes, a Assembly é substituída por MockAssembly, permitindo a substituição de dependências sem alterar o código de produção.

Comparação de DI com Service Locator e injeção manual

DI vs Service Locator — ambos os padrões resolvem o gerenciamento de dependências, mas de forma diferente. DI injeta dependências no objeto; Service Locator fornece um registro global do qual o objeto solicita as dependências por si só. DI declara explicitamente as dependências através do construtor (ou setter). Service Locator oculta as dependências — elas são solicitadas dentro do método, tornando a assinatura menos informativa. DI é mais fácil de testar: basta passar um mock ao construtor. Service Locator requer a configuração do registro global para cada teste.

CaracterísticaInjeção de DependênciaService LocatorInjeção manual
Visibilidade das dependênciasNo construtorOcultas no corpo do métodoExplícitas
TestesMock no construtorConfiguração do LocatorMock no construtor
Complexidade de configuraçãoRequer contêiner DIRegistro globalCriação manual
Sobrecarga em runtimeDagger — tempo de compilaçãoBusca em runtimeNenhuma

DI vs injeção manual — sem um contêiner DI, as dependências são criadas manualmente em fábricas ou no AppDelegate. Para 5-10 classes, a injeção manual é mais simples — não requer aprender Dagger ou Swinject. Para 50+ classes, a injeção manual torna-se problemática: construtores com 5-6 parâmetros, ordem de criação complexa, duplicação de código. Um contêiner DI automatiza esses processos e fornece gerenciamento claro do ciclo de vida. A injeção manual sem contêiner é uma boa escolha para projetos pequenos e protótipos.

Melhores práticas de Injeção de Dependência

Injeção por Construtor — padrão. Use sempre a Injeção por Construtor para dependências obrigatórias. Isso torna as dependências explícitas e o objeto sempre pronto para funcionar. Injeção por Setter — apenas para dependências opcionais (por exemplo, delegate ou listener). Injeção por Interface — não use, a menos que esteja escrevendo sua própria biblioteca DI. A Injeção por Construtor é a única maneira de garantir que um objeto seja criado em um estado válido.

Uma classe — uma responsabilidade. Se o construtor de uma classe requer 5+ parâmetros, provavelmente a classe viola o Princípio da Responsabilidade Única. Divida a classe em várias com menos dependências. Um sinal: se você escreve uma classe ServiceManager com 6 serviços diferentes — isso é o antipadrão God Object. Extraia a lógica de negócios para Use Cases (Interactors), cada um com 1-2 dependências.

kotlin
// ❌ Ruim: 6 dependências — God Object
class ProfileViewModel @Inject constructor(
    private val api: ApiService,
    private val db: Database,
    private val analytics: Analytics,
    private val prefs: Preferences,
    private val location: LocationProvider,
    private val notification: NotificationManager
)

// ✅ Bom: Use Cases com 1-2 dependências
class ProfileViewModel @Inject constructor(
    private val loadProfileUseCase: LoadProfileUseCase,
    private val trackAnalyticsUseCase: TrackAnalyticsUseCase
)

Escopo e ciclo de vida — escolha o escopo correto para cada dependência. Singletons: OkHttpClient, banco de dados, SharedPreferences. Escopo de funcionalidade: repositórios, Use Cases (se não tiverem estado). Transient: Value Objects, DateFormatter, analisadores. Erros de escopo são um problema comum: um singleton armazenando estado de tela causa vazamentos. No Android, o @ActivityScoped do Hilt resolve isso; no Swinject, use .container com cautela.

Perguntas frequentes

Por que preciso de DI se posso simplesmente usar new?

new cria um acoplamento forte entre classes — você não pode trocar a implementação sem alterar o código. Testar torna-se difícil: não é possível injetar um mock em vez de um serviço real. O SRP é violado: a classe é responsável tanto pela lógica de negócios quanto pela criação de dependências. DI resolve esses problemas injetando dependências externamente e trabalhando com abstrações.

Dagger Hilt ou Koin — qual escolher para Android?

Dagger Hilt é o padrão do Google, DI em tempo de compilação com geração de código, melhor desempenho e integração com Jetpack. Koin é DI em tempo de execução, mais fácil de configurar, mas mais lento e com erros em runtime. Escolha Hilt para projetos de produção. Koin é adequado para protótipos e aplicações pequenas.

Swinject é a única opção DI para iOS?

Não. Para iOS estão disponíveis: Swinject (runtime, popular), Needle (tempo de compilação da Uber), Dip (leve), Weaver (baseado em Sourcery). A Apple não fornece um contêiner DI integrado, mas a injeção manual via init é uma prática padrão. Para SwiftUI, a DI manual através de Environment ou @StateObject sem bibliotecas externas é frequentemente suficiente.

Posso usar DI sem um framework?

Sim. A injeção manual através do construtor é DI sem framework. Service Locator é uma alternativa sem framework. Fábricas e Factory Method também são formas de DI. Um framework (Dagger, Swinject) automatiza o registro rotineiro e a resolução de dependências, mas para 10-20 classes, o DI manual é suficiente.

DI é um padrão ou um princípio?

DI é uma técnica (template) que implementa o princípio da Inversão de Controle. Ao contrário dos padrões GoF, DI não tem uma estrutura rígida de 3-4 classes. DI é uma forma de organizar dependências, não um padrão de design. Os contêineres DI (Dagger, Swinject) são frameworks que automatizam essa técnica.

Resumo

  • DI — uma técnica de injeção de dependências externamente via construtor, setter ou método
  • Dagger Hilt — o padrão DI para Android com geração de código em tempo de compilação e @HiltViewModel
  • Swinject — DI em runtime para iOS com Container, Assembly e escopos
  • Injeção por Construtor — a abordagem preferida para dependências obrigatórias
  • Escopo — Singleton para serviços sem estado, escopo de funcionalidade para dependências de tela
  • Testes — DI simplifica a substituição de dependências por mocks sem alterar o código

Vamos desenvolver um aplicativo móvel chave na mão

A IT Sectr cria aplicativos para iOS e Android para startups e empresas desde 2017. Nós vamos aconselhá-lo e propor a melhor solução.

Discutir o projeto

Leia também