Dependency injection in swift

It is a design pattern used to achieve loose coupling, modularity, and better testability in your code. Instead of letting a class or struct create its own dependencies, you provide or inject those dependencies from the outside—usually at initialisation, through properties, or via method.

Why Use Dependency Injection?
  • Loosely coupled code: Components don’t hard-code their dependencies, making the codebase more flexible and maintainable.
  • Improved testability: You can inject mock or stub dependencies when unit testing, isolating the component under test.
  • Reusability and readability: Dependencies are explicit, making code clearer and easier to reason about.
1. Constructor Injection

Pass the dependency as a parameter in your object’s initialiser. This ensures the object is always fully configured after creation and promotes immutability.

protocol DatabaseProtocol {
    func fetch() -> [String]
}

class UserRepository {
    let database: DatabaseProtocol

    init(database: DatabaseProtocol) {
        self.database = database
    }
}

2. Property (Setter) Injection

Dependencies are assigned to a property after the object is created. This is often used for optional dependencies or when dependencies may change over the object’s lifecycle.

class UserRepository {
    var database: DatabaseProtocol?
}
let repo = UserRepository()
repo.database = SQLiteDatabase()
3. Method Injection

Pass the dependency as an argument when calling a specific method. This is ideal for temporary dependencies.

class Printer {
    func printDocument(using driver: PrintDriverProtocol) {
        driver.print()
    }
}
In summary
  • Use constructor injection for all required dependencies. This makes them explicit and avoids uninitialized state.
  • Use property or method injection when a dependency is optional or may change.
  • Always prefer protocols for dependencies, allowing different implementations.