Swift DI is a component-based DI (Dependency Injection) library designed specifically for Swift.
The DI container is implemented as a value type to take advantage of Swift's features. No external executables are needed; it is implemented using standard Swift language features and macros. By using only Swift features, the DI container can easily adapt to new versions of Swift, and users can also extend it to customize behaviors. Multi-module compliance, Sendable conforming, and instance overwriting are supported.
To add a dependency on the package, declare it in your Package.swift
:
.package(url: "https://github.com/sidepelican/swift-di.git", from: "1.0.0"),
and to your application target, add DI
to your dependencies:
.product(name: "DI", package: "swift-di"),
In Swift DI, you need to explicitly prepare a key value for each instance. We recommend providing static Key
properties as extensions of AnyKey
.
import DI
extension AnyKey {
// (1) Define DI keys.
static let name = Key<String>()
static let foo = Key<Foo>()
}
@Component(root: true)
struct RootComponent {
// (2) Define value for the key.
@Provides(.name)
var name: String { "RootComponent" }
@Provides(.foo)
var foo: Foo {
// (3) Get value using `get`.
Foo(name: get(.name))
}
var childComponent: ChildComponent {
ChildComponent(parent: self)
}
}
@Component
struct ChildComponent {
// (4) Child component can override parent component's key.
@Provides(.name)
var name: String { "ChildComponent" }
func testFooName() {
print(get(.foo)) // => Foo(name: "ChildComponent")
}
}
When priority
is set, values with a lower priority will not overwrite those with a higher priority.
@Component(root: true)
struct TestComponent {
@Provides(.name, priority: .test)
var name: String { "TestComponent" }
}
@Component
struct AppComponent {
@Provides(.name, priority: .default)
var name: String { "AppComponent" }
func printName() {
print(get(.name))
}
}
let component = AppComponent(parent: TestComponent())
component.printName() // TestComponent
By using bind(_:forKey:)
, you can procedurally override values.
@Component
struct ParentComponent {
@Provides(.name)
var name: String = "ParentComponent"
@Provides(.age)
var age: Int = 42
}
@Component
struct ChildComponent {
@Provides(.name)
var name: String = "ChildComponent"
func printProfile() {
print("name=\(get(.name)), age=\(get(.age))")
}
}
var component = ChildComponent(parent: ParentComponent())
component.printProfile() // name=ChildComponent, age=42
component.bind(10, forKey: .age)
component.printProfile() // name=ChildComponent, age=10
Currently, there is no official method due to some challenges. However, it is easy to implement individually. Define the following class:
import Foundation
public final class SingletonStorage: @unchecked Sendable {
private let lock = NSRecursiveLock()
private var storage: [Int: Any] = [:]
public init() {}
public func callAsFunction<T>(key: Int = #line, _ make: @autoclosure () -> T) -> T {
lock.lock()
defer { lock.unlock() }
if let value = storage[key] {
return value as! T
}
let value = make()
storage[key] = value
return value
}
}
Then, you can easily use singletons by put this as a property of your Component.
@Component
struct SceneComponent {
private let singleton = SingletonStorage()
@Provides(.urlSession)
var urlSession: URLSession {
singleton(URLSession())
}
}
This is a bug in the Swift compiler. It has been fixed but not yet released. As a workaround, explicitly add Sendable
to your component.
You can avoid the warning by wrapping the value in a tuple.
struct RootComponent: Sendable {
init() {
self.logger = Logger(label: "app")
self.awsClient = AWSClient(
logger: (logger) // avoid warning
)
initContainer(parent: self)
}
@Provides(.logger)
let logger: Logger
...
}