What is dependency inversion in iOS?
The precise definition — and the distinction that trips people up
Dependency Inversion Principle (DIP):
- High-level modules should not depend on low-level modules; both should depend on abstractions.
- Abstractions should not depend on details; details should depend on abstractions.
Dependency Injection (DI) is a technique — passing a dependency in rather than constructing it. You can have DI without DIP:
// Injection WITHOUT inversion
final class ProfileViewModel {
private let repository: NetworkUserRepository // still concrete!
init(repository: NetworkUserRepository) { self.repository = repository }
}
// Injection WITH inversion
protocol UserRepository {
func fetchUser(id: String) async throws -> User
}
final class ProfileViewModel {
private let repository: UserRepository
init(repository: UserRepository) { self.repository = repository }
}
final class NetworkUserRepository: UserRepository {
func fetchUser(id: String) async throws -> User { ... }
}
Why “inversion” is the right word
The abstraction is conceptually owned by the high-level/policy side, not the low-level detail.
Where this shows up
Clean Architecture, multi-team contracts, third-party SDK wrapping, testability everywhere.
A common half-measure worth naming
Injecting a concrete class instance is better than a singleton, but it’s not full DIP. Sometimes that’s fine — introducing an abstraction with exactly one real implementation and no test-double need is ceremony without payoff.
| Dependency Injection | Dependency Inversion | |
|---|---|---|
| What it is | A technique: pass dependencies in | A design principle: both sides depend on a shared abstraction |
| Can exist alone? | Yes — can inject a concrete type | No — requires an abstraction layer |
| What it solves | Configurability, testability | True decoupling |
One-liner if pressed: “Dependency injection is passing a dependency in rather than constructing it; dependency inversion is the stronger claim that both the high-level and low-level code should depend on a shared abstraction that the high-level side effectively owns, so the low-level detail conforms to what the policy needs rather than the policy adapting to whatever the detail happens to expose.”