A minimalistic, lightweight, type-safe dependency injection framework for Swift, inspired by swift-dependencies and SwiftLee's dependency injection approach.
This library provides a simple way to manage dependencies in your Swift applications using property wrappers. It allows you to:
- Define dependencies using a protocol-based approach
- Easily inject dependencies into your types
- Swap implementations at runtime (especially useful for testing)
- Use a clean, declarative syntax
- Swift 6.0+
- Platforms:
- macOS 10.15+
- iOS 15+
- tvOS 15+
- watchOS 6+
- visionOS 1+
- Open your Xcode project.
- Navigate to File > Swift Packages > Add Package Dependency…
- Enter the repository URL:
https://github.com/emarashliev/dependency.git
Include the package dependency in your Package.swift file as shown below:
let package = Package(
name: "YourProject",
dependencies: [
.package(url: "https://github.com/emarashliev/dependency.git", branch: "main")
],
targets: [
.target(
name: "YourProject",
dependencies: ["Dependency"]
)
]
)There are two ways to define a dependency:
protocol MyService: Sendable {
func doSomething() -> String
}
struct DefaultMyService: MyService {
func doSomething() -> String {
"Default implementation"
}
}
struct MyServiceKey: DependencyKey {
static let currentValue: MyService = DefaultMyService()
}
extension DependencyValues {
var myService: MyService {
get { self[MyServiceKey.self] }
set { self[MyServiceKey.self] = newValue }
}
}protocol AnotherService: Sendable {
func performAction() -> String
}
struct DefaultAnotherService: AnotherService, DependencyKey {
static let currentValue: AnotherService = DefaultAnotherService()
func performAction() -> String {
"Default implementation"
}
}Once defined, you can access your dependencies in any type:
struct MyFeature {
// Access using the keypath
@Dependency(\.myService) var myService
// Or directly with the type
@Dependency(DefaultAnotherService.self) var anotherService
func doWork() -> String {
let result1 = myService.doSomething()
let result2 = anotherService.performAction()
return result1 + " " + result2
}
}For testing or development purposes, you can override dependencies:
let myFeature = withDependencies {
$0.myService = MockMyService()
$0[DefaultAnotherService.self] = MockAnotherService()
} operation: {
MyFeature()
}
// Now myFeature uses the mock implementations@Test
func featureWithMocks() {
let feature = withDependencies {
// Override dependencies with mocks
$0.myService = MockMyService()
} operation: {
MyFeature()
}
// Now feature uses MockMyService instead of the default implementation
#expect(feature.doWork() == "Mock implementation")
}The library consists of a few key components:
DependencyKey: A protocol defining how to register a dependencyDependencyValues: A container for all registered dependencies@Dependency: A property wrapper for accessing dependencieswithDependencies: A function for overriding dependencies in a specific scope
MIT