A lightweight dependency injection library for Swift, designed for non-view scenarios in SwiftUI projects.
- ✅ Familiar API: SwiftUI.Environment-like declarative syntax
- ✅ Concurrency Safe: Sendable constraint enforced, @TaskLocal based
- ✅ Three Scopes: Automatic switching between
default,test, andpreview - ✅ Lightweight: Zero dependencies, pure Swift (~200 lines of code)
- ✅ Swift 6: Full support for strict concurrency checking
- Swift 6.0+
- iOS 15+ / macOS 12+ / tvOS 15+ / watchOS 8+ / visionOS 1+
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/fatbobman/TinyDependency.git", from: "0.3.0")
]Or add via Xcode: File → Add Package Dependencies
protocol Logger: Sendable {
func log(_ message: String)
}
struct ProductionLogger: Logger {
func log(_ message: String) {
print("[\(Date())] \(message)")
}
}
struct MockLogger: Logger {
func log(_ message: String) {
// Silent or log to memory
}
}import TinyDependency
private struct LoggerKey: DependencyKey {
static let defaultValue: Logger = ProductionLogger()
static let testValue: Logger = MockLogger()
static let previewValue: Logger = MockLogger()
}
extension DependencyValues {
var logger: Logger {
get { self[LoggerKey.self] }
set { self[LoggerKey.self] = newValue }
}
}class UserService {
@Dependency(\.logger) var logger
func createUser(name: String) async {
logger.log("Creating user: \(name)")
// Business logic...
}
}import Testing
@testable import YourApp
@Test
func testUserService() async {
let customLogger = CustomMockLogger()
await withDependencies {
$0.logger = customLogger
} operation: {
let service = UserService()
await service.createUser(name: "Alice")
#expect(customLogger.messages.contains("Creating user: Alice"))
}
}TinyDependency automatically selects appropriate default values based on the environment:
| Environment | Value Used |
|---|---|
| App Runtime | defaultValue |
| Test Environment | testValue |
| Xcode Preview | previewValue |
Regular app runs use defaultValue in both Debug and Release builds. testValue is only selected when the process is actually running in a test environment.
You can also override the automatic detection by setting TINY_DEPENDENCY_CONTEXT to default, test, or preview. The value live is also accepted as an alias for default.
This is especially useful for integration checks that run outside the test runner, such as subprocess-based verification, command-line tools, or custom CI scripts. For example, you can force testValue in a normal process with:
env TINY_DEPENDENCY_CONTEXT=test swift run MyAppProbeSupported values:
defaultlive(alias ofdefault)testpreview
Unknown values are ignored and the library falls back to automatic detection.
You can omit testValue and previewValue in DependencyKey - they will fall back to defaultValue:
private struct LoggerKey: DependencyKey {
static let defaultValue: Logger = ProductionLogger()
// testValue and previewValue automatically use defaultValue
}withDependencies {
$0.logger = Logger1()
} operation: {
// Uses Logger1
withDependencies {
$0.logger = Logger2()
} operation: {
// Uses Logger2
}
// Back to Logger1
}await withDependencies {
$0.database = MockDatabase()
} operation: {
await someAsyncWork()
}Based on @TaskLocal, dependencies are automatically isolated between tasks:
await withTaskGroup(of: Void.self) { group in
group.addTask {
await withDependencies {
$0.logger = Logger1()
} operation: {
// Task 1 uses Logger1
}
}
group.addTask {
await withDependencies {
$0.logger = Logger2()
} operation: {
// Task 2 uses Logger2, unaffected by Task 1
}
}
}Enforcing Sendable constraint simplifies concurrency safety design:
- No complex locking mechanisms needed
- Thread safety guaranteed at compile time
- Perfect alignment with Swift 6's strict concurrency model
| Feature | TinyDependency | Pointfree Dependencies |
|---|---|---|
| Complexity | Lightweight (~200 LOC) | Feature-rich |
| Sendable | Required | Optional |
| Scopes | 3 fixed scopes | Flexible scope system |
| Learning Curve | Low (like Environment) | Medium |
| Target Scenario | Non-view usage | All scenarios |
TinyDependency focuses on simple scenarios. For advanced features (dependency tracking, automatic mock generation, etc.), consider Pointfree Dependencies.
See Tests/TinyDependencyTests and Examples.md for complete usage examples.
MIT License