https://github.com/dfed/safedi
Compile-time-safe dependency injection in Swift
https://github.com/dfed/safedi
dependency-injection dependency-management swift
Last synced: about 1 month ago
JSON representation
Compile-time-safe dependency injection in Swift
- Host: GitHub
- URL: https://github.com/dfed/safedi
- Owner: dfed
- License: mit
- Created: 2023-11-23T02:41:03.000Z (over 2 years ago)
- Default Branch: main
- Last Pushed: 2026-04-02T00:00:55.000Z (2 months ago)
- Last Synced: 2026-04-02T01:56:23.322Z (2 months ago)
- Topics: dependency-injection, dependency-management, swift
- Language: Swift
- Homepage:
- Size: 7.14 MB
- Stars: 179
- Watchers: 6
- Forks: 8
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Contributing: Contributing.md
- Funding: .github/FUNDING.yml
- License: LICENSE
Awesome Lists containing this project
- awesome-swift - SafeDI - Compile-time safe dependency injection. (Libs / Dependency Injection)
- trackawesomelist - SafeDI (⭐124) - Compile-time safe dependency injection in Swift 6. (Recently Updated / [Who Wants to Be a Millionare](https://www.boardgamecapital.com/who-wants-to-be-a-millionaire-rules.htm))
- awesome-ios - SafeDI - Compile-time safe dependency injection in Swift 6. (Dependency Injection / Getting Started)
- fucking-awesome-swift - SafeDI - Compile-time safe dependency injection. (Libs / Dependency Injection)
- fucking-awesome-ios - SafeDI - Compile-time safe dependency injection in Swift 6. (Dependency Injection / Getting Started)
- awesome-ios-star - SafeDI - Compile-time safe dependency injection in Swift 6. (Dependency Injection / Getting Started)
README
# SafeDI
[](https://github.com/dfed/SafeDI/actions?query=workflow%3ACI+branch%3Amain)
[](https://codecov.io/gh/dfed/SafeDI)
[](https://spdx.org/licenses/MIT.html)
[](https://swiftpackageindex.com/dfed/SafeDI)
[](https://swiftpackageindex.com/dfed/SafeDI)
Compile-time-safe dependency injection without the boilerplate. No containers. No service locators. No hand-written DI types.
## Why teams choose SafeDI
- **Dependency injection that feels natural.** Get the simplicity of manual dependency injection without ceremony.
- **Compile-time graph validation.** If the code compiles, the dependency graph is valid.
- **Scoped runtime values.** Make your real logged-in `User` available non-optionally to any type in the subtree with just a single macro decoration.
- **Full-graph mocks.** Generated from your real dependency graph, `mock()` lets you override any branch for easy previews and tests.
- **Architecture-independent.** SwiftUI or UIKit, coordinators or MVVM, one module or hundreds — SafeDI fits what you already have.
- **Clear failures.** SafeDI flags unsolvable dependency graphs, outlining the problem and suggesting fixes.
## The core concept
SafeDI reads your code, validates your dependencies, and generates production and mock dependency trees—all during project compilation.
Opting a type into the SafeDI dependency tree is simple: add the `@Instantiable` macro to your type declaration, and decorate each dependency with a macro that indicates its lifecycle. Here is what a notes app might look like in SafeDI:
```swift
// `NotesApp` is the root of the dependency graph. SafeDI generates its `public init()`.
@Instantiable(isRoot: true) @main
public struct NotesApp: App, Instantiable {
public init(
userService: UserService,
nameEntryViewBuilder: Instantiator,
loggedInViewBuilder: Instantiator
) { … }
public var body: some Scene {
WindowGroup {
if let user = userService.user {
// Forward the authenticated user into the logged-in subtree.
loggedInViewBuilder.instantiate(user)
} else {
nameEntryViewBuilder.instantiate()
}
}
}
@ObservedObject @Instantiated private var userService: UserService
@Instantiated private let nameEntryViewBuilder: Instantiator
@Instantiated private let loggedInViewBuilder: Instantiator
}
@Instantiable
public struct LoggedInView: View, Instantiable {
public init(user: User, userService: UserService, noteStorage: NoteStorage) { … }
public var body: some View { … }
// `user` is a runtime value forwarded in at this boundary.
@Forwarded private let user: User
// `userService` is received from an ancestor in the tree.
@Received private let userService: UserService
// `noteStorage` is created by `LoggedInView` and lives for its lifetime.
@Instantiated private let noteStorage: NoteStorage
}
```
`User` is a runtime-derived value. It is forwarded once at the logged-in boundary and received later by the types that need it—non-optional, scoped to the subtree where it exists.
This is the core SafeDI model:
- write normal Swift types,
- declare dependencies where they live,
- let SafeDI validate and generate the wiring.
For a comprehensive explanation of SafeDI’s macros and their usage, please read [the Macros section of our manual](Documentation/Manual.md#macros).
## Tests and previews from real feature roots
Decorate a type with `@Instantiable(generateMock: true)` and SafeDI generates a `static func mock(…) -> Type` method that builds the full dependency subtree for that type. The same declarations that define the production graph generate the test and preview graphs.
For a `LoggedInView` decorated with `@Instantiable(generateMock: true)`, if every dependency can be mocked, calling `mock()` with no arguments works. The `User` mock below demonstrates how `mockOnly` can supply a default for a forwarded runtime value:
```swift
#Preview {
LoggedInView.mock()
}
// Declarations can participate only in mock generation via `mockOnly`.
@Instantiable(mockOnly: true)
extension User {
public static func mock() -> User {
User(name: "Mock User")
}
}
```
For previews and tests that need real data, pass forwarded values directly and use `safeDIOverrides` to reach into the subtree:
```swift
#Preview {
LoggedInView.mock(
user: User(name: "dfed"),
safeDIOverrides: .init(
noteStorage: .init(defaultNote: "dfed says hello")
)
)
}
```
`safeDIOverrides` is a generated `struct` whose fields mirror the subtree SafeDI built. SafeDI still wires the rest of the graph around each override, so customizations compose with the subtree instead of replacing it.
## Features
✓ Compile-time safe✓ Thread safe✓ Hierarchical dependency scoping
✓ Constructor injection✓ Multi-module support✓ Dependency inversion support
✓ Transitive dependency solving✓ Cycle detection✓ Architecture independent
✓ No DI-specific types or generics required✓ Full-graph mocks✓ Clear errors: never debug generated code
## Getting started
Three steps to integrate:
1. Add `.package(url: "https://github.com/dfed/SafeDI.git", from: "2.0.0")` to your `Package.swift` dependencies.
2. Attach the `SafeDIGenerator` build tool plugin to your first-party target(s).
3. Decorate your app’s root type with `@Instantiable(isRoot: true)` and add `@Instantiable` to the dependencies it reaches.
Working sample projects live in the [Examples folder](Examples/) — clone, open, and build. The [Manual](Documentation/Manual.md#installation) covers Xcode projects, multi-module packages, custom build systems, and prebuild scripts in depth.
If you are migrating an existing project to SafeDI, follow our [migration guide](Documentation/Manual.md#migrating-to-safedi). If you are upgrading from SafeDI 1.x, follow the [1.x → 2.x migration guide](Documentation/Manual.md#migrating-from-safedi-1x-to-2x).
## Comparing SafeDI to other DI libraries
SafeDI is closest in spirit to [Needle](https://github.com/uber/needle) and [Weaver](https://github.com/scribd/Weaver): all three validate the dependency graph at compile time and support hierarchical scoping, letting runtime-derived values like an authenticated user live non-optionally inside a subtree. Where Needle asks you to maintain a dependency protocol for every type, and Weaver keeps a separate container declaration alongside your code, SafeDI leaves your types untouched — the macros *are* the integration.
[Factory](https://github.com/hmlongco/Factory) and [swift-dependencies](https://github.com/pointfreeco/swift-dependencies) take a container/environment approach that shines for scalar dependencies like a `Clock` or a `URLSession`. SafeDI is built around the object graph itself — hierarchical scoping makes graph-local runtime values (an auth token, a logged-in user) first-class subtree dependencies, received non-optionally where they’re actually needed. [Swinject](https://github.com/Swinject/Swinject) goes further in that runtime-lookup direction and offers no compile-time validation at all.
SwiftUI’s own `Environment` is a useful mental model for a dependency tree — but without compile-time validation. SafeDI applies that tree shape to the full object graph and guarantees it resolves.
Across all of these, SafeDI is the only Swift DI library that generates full-graph mocks from your real dependency graph, and the only hierarchical DI library whose integration errors surface as Swift macro diagnostics with fix-its directly in your IDE.
## Contributing
I’m glad you’re interested in SafeDI, and I’d love to see where you take it. Please review the [contributing guidelines](Contributing.md) prior to submitting a Pull Request.
Thanks for being part of this journey, and happy injecting!
## Author
SafeDI was created by [Dan Federman](https://github.com/dfed), the architect of Airbnb’s closed-source Swift dependency injection system. Following his tenure at Airbnb, Dan developed SafeDI to share a modern, compile-time-safe dependency injection solution with the Swift community.
Dan has a proven track record of maintaining open-source libraries: he co-created [Valet](https://github.com/square/Valet) and has been maintaining the repo since its debut in 2015.
## Acknowledgements
Special thanks to [@kierajmumick](http://github.com/kierajmumick) for helping shape the early design of SafeDI.