Dependency Injection em Swift: princípios, exemplos e implementação

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

O que é Dependency Injection e por que é necessário em Swift

Dependency Injection (DI) é um padrão de projeto no qual um objeto recebe suas dependências externamente, em vez de criá-las por conta própria. No contexto do Swift para iOS e macOS, DI ajuda a tornar o código mais flexível, testável e sustentável. Em vez de acoplamento rígido de classes via init() ou let, você passa as dependências através do construtor, propriedade ou método.

Principais vantagens do DI:

  • Testabilidade: você substitui facilmente objetos reais por mocks ou stubs em testes unitários.
  • Baixo acoplamento: as classes dependem de abstrações (protocolos), não de implementações concretas.
  • Simplificação de refatoração: alterações em uma parte do código não quebram outra.
  • Arquitetura limpa: o código segue o princípio da responsabilidade única e o princípio da inversão de dependências (DIP do SOLID).

Em Swift, DI é especialmente importante ao trabalhar com UIKit, SwiftUI, CoreData, rede e outros serviços. Sem ele, você corre o risco de ter "objetos divindade" e dificuldades com reutilização de código.

Principais formas de injeção de dependências em Swift

1. Injeção via inicializador (Constructor Injection)

A forma mais comum e recomendada. As dependências são passadas na criação do objeto e tornam-se imutáveis (let).

protocol NetworkServiceProtocol {
    func fetchData() -> String
}

class NetworkService: NetworkServiceProtocol {
    func fetchData() -> String {
        return "Dados do servidor"
    }
}

class ViewModel {
    private let networkService: NetworkServiceProtocol

    // Injeção via inicializador
    init(networkService: NetworkServiceProtocol) {
        self.networkService = networkService
    }

    func loadData() -> String {
        return networkService.fetchData()
    }
}

// Uso
let service = NetworkService()
let viewModel = ViewModel(networkService: service)
print(viewModel.loadData()) // "Dados do servidor"

Vantagens: dependências explícitas, objeto sempre em estado válido. Desvantagem: com muitas dependências, o construtor torna-se extenso.

2. Injeção via propriedade (Property Injection)

A dependência é definida após a criação do objeto. Frequentemente usado para serviços opcionais ou ao trabalhar com Storyboard/XIB.

class UserViewController: UIViewController {
    var analyticsService: AnalyticsServiceProtocol?

    override func viewDidLoad() {
        super.viewDidLoad()
        analyticsService?.trackEvent("Tela aberta")
    }
}

// Injeção
let vc = UserViewController()
vc.analyticsService = AnalyticsService()

Desvantagem: a propriedade precisa ser opcional ou ter um valor padrão, o que pode causar erros em tempo de execução se a dependência não for definida.

3. Injeção via método (Method Injection)

A dependência é passada como parâmetro do método. Adequado para situações em que a dependência é necessária apenas para uma operação.

class DataManager {
    func processData(using service: NetworkServiceProtocol) -> String {
        let rawData = service.fetchData()
        return "Processado: \(rawData)"
    }
}

let manager = DataManager()
let result = manager.processData(using: NetworkService())
print(result) // "Processado: Dados do servidor"

Exemplo de implementação de DI em aplicativo iOS

Vamos considerar um exemplo real: um aplicativo para exibir uma lista de usuários. Criaremos protocolos e classes com injeção de dependências.

// 1. Protocolos
protocol UserRepositoryProtocol {
    func getUsers() -> [String]
}

protocol UserViewModelProtocol {
    var users: [String] { get }
    func loadUsers()
}

// 2. Implementação do repositório
class UserRepository: UserRepositoryProtocol {                

Blogs

Book Recommendations