What is Dependency Injection in Swift and Why Do You Need It?
\n\nDependency Injection (DI) is one of the key design principles in modern iOS development. If you are just starting your journey in Swift, you may have already encountered a situation where one class creates objects of another class directly inside itself. This is called tight coupling, and it makes your code difficult to test and maintain.
\n\nDependency Injection solves this problem: instead of a class creating its own dependencies, they are "injected" from the outside. This makes the code more flexible, testable, and compliant with the Single Responsibility principle from SOLID.
\n\nIn this article, we will break down how Dependency Injection works in Swift with practical examples and show how to implement this practice in your iOS and macOS projects.
\n\nMain Types of Dependency Injection in Swift
\n\nThere are three main ways to inject dependencies in Swift. The choice of a specific method depends on your application's architecture.
\n\n1. Constructor Injection
\nThe most common and recommended method. Dependencies are passed through the class's init. This guarantees that the object is always in a valid state.
// Protocol for the service\nprotocol DataServiceProtocol {\n func fetchData() -> String\n}\n\n// Real implementation\nclass DataService: DataServiceProtocol {\n func fetchData() -> String {\n return "Data from server"\n }\n}\n\n// Class that uses the service\nclass ViewModel {\n private let dataService: DataServiceProtocol\n \n // Dependency is injected through the initializer\n init(dataService: DataServiceProtocol) {\n self.dataService = dataService\n }\n \n func loadData() {\n let data = dataService.fetchData()\n print("Loaded: \\(data)")\n }\n}\n\n// Usage\nlet service = DataService()\nlet viewModel = ViewModel(dataService: service)\nviewModel.loadData()\n\n2. Property Injection
\nUsed when the dependency is not mandatory for the object to function or may change during its lifecycle. It is important not to forget to set the property before use.
\n\nclass NetworkManager {\n var urlSession: URLSession?\n \n func makeRequest() {\n guard let session = urlSession else {\n print("Error: session not set")\n return\n }\n // Work with session\n print("Request made via \\(session)")\n }\n}\n\n// Usage\nlet manager = NetworkManager()\nmanager.urlSession = URLSession.shared // Dependency injection\nmanager.makeRequest()\n\n3. Method Injection
\nIdeal when a dependency is only needed to execute one specific method.
\n\nclass PaymentProcessor {\n func processPayment(amount: Double, using gateway: PaymentGatewayProtocol) -> Bool {\n // gateway is used only here\n return gateway.charge(amount: amount)\n }\n}\n\nprotocol PaymentGatewayProtocol {\n func charge(amount: Double) -> Bool\n}\n\nclass StripeGateway: PaymentGatewayProtocol {\n func charge(amount: Double) -> Bool {\n print("Payment \\(amount) via Stripe")\n return true\n }\n}\n\n// Usage\nlet processor = PaymentProcessor()\nlet stripe = StripeGateway()\nlet result = processor.processPayment(amount: 99.99, using: stripe)\n\nWhy Should You Use Dependency Injection in iOS Projects?
\n\nMany beginner Swift developers ignore DI, considering it an unnecessary complication. However, in practice, the benefits become apparent very quickly:
\n\n- \n
- Simplified Testing (Unit Testing): You can easily replace real services with