Swinject — Dependency Injection Container for Swift (iOS/macOS)
Dependency Injection (DI) is one of the key SOLID principles, allowing you to create loosely coupled, testable, and scalable code. In the Swift world for iOS and macOS development, one of the most popular libraries implementing DI is Swinject. It is a lightweight yet powerful dependency injection container that simplifies object management and their lifecycle.
In this article, we will take a detailed look at how Swinject works, how to install it, and how to use it in real projects.
What is Dependency Injection and why do you need a container?
Dependency injection is a technique where an object receives its dependencies from the outside, rather than creating them itself. This increases flexibility and simplifies testing. A DI container (e.g., Swinject) automates the process of creating and linking objects, saving the developer from writing boilerplate code.
Installing Swinject
Swinject can be installed using Swift Package Manager, CocoaPods, or Carthage. Let's look at the most modern method — SPM:
// In Xcode: File → Swift Packages → Add Package Dependency// URL: https://github.com/Swinject/Swinject.git// Version: from 2.8.0 and aboveOr add it to Package.swift:
dependencies: [ .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")]Main Components of Swinject
- Container — the main object where dependencies are registered and resolved.
- Service — the type (protocol or class) we want to inject.
- Registration — the process of binding an abstraction (protocol) to a concrete implementation.
- Scope — manages the object's lifecycle (transient, singleton, etc.).
Basic Usage Example
Suppose we have a protocol NetworkServiceProtocol and its implementation NetworkService:
import Swinject
protocol NetworkServiceProtocol { func fetchData() -> String}
class NetworkService: NetworkServiceProtocol { func fetchData() -> String { return "Data received" }}
// Create a containerlet container = Container()
// Register the dependencycontainer.register(NetworkServiceProtocol.self) { _ in NetworkService() }
// Resolve (get) an instancelet service = container.resolve(NetworkServiceProtocol.self)!print(service.fetchData()) // "Data received"Injecting Dependencies into ViewController (iOS)
Most often, Swinject is used to inject services into controllers. Let's look at an example with a view model:
import Swinject
// Protocol and implementation of the view modelprotocol UserViewModelProtocol { func getUsers() -> [String]}
class UserViewModel: UserViewModelProtocol { private let networkService: NetworkServiceProtocol init(networkService: NetworkServiceProtocol) { self.networkService = networkService } func getUsers() -> [String] { // Use networkService for loading return ["Alice", "Bob"] }}
// Registration in the containercontainer.register(UserViewModelProtocol.self) { resolver in let network = resolver.resolve(NetworkServiceProtocol.self)! return UserViewModel(networkService: network)}
// Usage in the controllerclass UsersViewController: UIViewController { var viewModel: UserViewModelProtocol? override func viewDidLoad() { super.viewDidLoad() viewModel = container.resolve(UserViewModelProtocol.self) print(viewModel?.getUsers() ?? []) }}Object Lifecycles (Scopes)
Swinject supports several scopes:
- .transient
Also in library
Recommendations