Dependency Injection Container for Swift (iOS/macOS): Swinject
In modern iOS and macOS development with Swift, architectural patterns play a key role in creating maintainable and testable code. One of the most powerful tools for implementing the Dependency Inversion Principle is Swinject — a lightweight yet functional DI container written in Swift. Swinject allows centralized management of object creation and their dependencies, freeing the developer from manually constructing the object graph.
What is Dependency Injection and why do you need a container?
Dependency Injection (DI) is a technique where an object receives its dependencies from the outside rather than creating them itself. This reduces code coupling and simplifies unit testing. However, in large projects, manual dependency injection leads to cumbersome code. This is where a DI container comes to the rescue, which:
- Automates object creation and binding
- Manages object lifetimes (singletons, new instances)
- Simplifies implementation swapping (e.g., for tests)
- Improves code readability and maintainability
Installing Swinject
Swinject can be installed via Swift Package Manager, CocoaPods, or Carthage. For SPM, add to Package.swift:
dependencies: [ .package(url: "https://github.com/Swinject/Swinject.git", from: "2.8.0")]Or via CocoaPods:
pod 'Swinject', '~> 2.8'Core Concepts of Swinject
The library is based on several key components:
- Container — the main registrar and resolver of dependencies
- Service — the type being registered (usually a protocol or class)
- Component — the concrete implementation of a service
- Scope — defines the object lifetime (transient, graph, container, weak)
Practical Example: Registration and Resolution of Dependencies
Consider a typical scenario — an application with an authentication service and a controller that uses it.
// Service protocolprotocol AuthServiceProtocol { func login(username: String, password: String) -> Bool}
// Implementationclass AuthService: AuthServiceProtocol { func login(username: String, password: String) -> Bool { // Authentication logic return username == "admin" && password == "1234" }}
// Controller depending on AuthServiceProtocolclass LoginViewController { private let authService: AuthServiceProtocol // Injection via initializer init(authService: AuthServiceProtocol) { self.authService = authService } func authenticate(user: String, pass: String) { if authService.login(username: user, password: pass) { print("Successful login") } else { print("Invalid credentials") } }}
// Container setuplet container = Container()container.register(AuthServiceProtocol.self) { _ in AuthService() }container.register(LoginViewController.self) { r in LoginViewController(authService: r.resolve(AuthServiceProtocol.self)!)}
// Usagelet controller = container.resolve(LoginViewController.self)!controller.authenticate(user: "admin", pass: "1234") // Successful loginManaging Object Lifetimes (Scopes)
Swinject supports four built-in scopes:
- .transient — a new instance is created each time (default)
- .graph — one instance per resolution graph (used by default for most cases)
- .container — singleton within the container
- .weak — weak reference, object
Also in library
Elasticsearch .NET for C# (.NET): Complete Guide with Examples
Working with XML in TypeScript: Libraries for Parsing and Creating XML
Parsing HTML in PHP: DOMDocument, SimpleXML, and cURL for Web Scraping
Working with ZIP Archives in C++: libzip and zlib Libraries