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 {