Injeção de Dependência em Swift: Guia Prático para iOS

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: Por que a Injeção de Dependência é uma Necessidade

No desenvolvimento moderno para iOS e macOS, a limpeza e a testabilidade do código estão em primeiro plano. Um dos princípios-chave para alcançar esses objetivos é a Injeção de Dependência (DI). Sem DI, as classes frequentemente se tornam "fortemente acopladas" (tightly coupled), o que dificulta sua reutilização, modificação e, principalmente, a escrita de testes unitários.

Na prática, a Injeção de Dependência em Swift significa que um objeto recebe suas dependências de fora, em vez de criá-las ele mesmo. Isso pode parecer uma mudança pequena, mas muda radicalmente a arquitetura do aplicativo. Neste artigo, vamos abordar os principais padrões de DI em Swift: Constructor Injection, Property Injection e o uso de um container DI com o exemplo do Swinject. Cada método será ilustrado com exemplos práticos para iOS/macOS.

Pronto para tornar seu código mais flexível e testável? Então vamos começar.



1. Constructor Injection: A Maneira Mais Confiável

Constructor Injection é a injeção de dependências através do inicializador (init) da classe. Este método é considerado o mais preferível, pois garante que o objeto esteja sempre em um estado válido: todas as suas dependências são obrigatórias e imutáveis.

Vamos considerar um exemplo. Suponha que temos uma classe NetworkService, que lida com requisições HTTP, e uma classe UserViewModel, que usa este serviço.

// Protocolo para abstraçãoprotocol NetworkServiceProtocol {    func fetchUsers() async throws -> [User]}

// Implementação realclass RealNetworkService: NetworkServiceProtocol { func fetchUsers() async throws -> [User] { // Requisição HTTP real return [] }}

// ViewModel com Constructor Injectionclass UserViewModel { private let networkService: NetworkServiceProtocol

// A dependência é passada através do inicializador init(networkService: NetworkServiceProtocol) { self.networkService = networkService }

func loadUsers() async { do { let users = try await networkService.fetchUsers() print("Carregados \\(users.count) usuários") } catch { print("Erro: \\(error)") } }}

// Usolet service = RealNetworkService()let viewModel = UserViewModel(networkService: service)

Vantagens:

  • A dependência é explicitamente declarada e obrigatória.
  • O objeto não pode ser criado sem dependências.
  • Ideal para testes unitários: é fácil substituir a implementação real por um Mock.

Desvantagens:

  • Se houver muitas dependências, o inicializador se torna extenso.
  • Não é adequado para dependências opcionais.

Este método é o "padrão ouro" da DI. Use-o sempre que possível.



2. Property Injection: Flexibilidade para Dependências Opcionais

Property Injection é a injeção de dependências através de propriedades do objeto. Geralmente é usado quando a dependência não é obrigatória para o funcionamento do objeto ou quando não é possível passá-la pelo inicializador (por exemplo, em Storyboard/UIKit).

Um exemplo clássico é um ViewController no iOS que é criado a partir de um Storyboard. Você não pode alterar seu init, então usa uma propriedade.

protocol LoggerProtocol {    func log(message: String)}

class ConsoleLogger: LoggerProtocol { func log(message: String) { print("LOG: \\(message)") }}

class MyViewController: UIViewController { // Dependência opcional var logger: LoggerProtocol?

override func viewDidLoad() { super.viewDidLoad() logger?.log(message: "View did load") // Se logger não estiver definido, nada acontece }}

// Configuração (exemp

Blogs

Book Recommendations