Compile-time-safe dependency injection without the boilerplate. No containers. No service locators. No hand-written DI types.
- 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
Useravailable 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.
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:
// `NotesApp` is the root of the dependency graph. SafeDI generates its public `init()`.
@Instantiable(isRoot: true) @main
public struct NotesApp: App, Instantiable {
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<NameEntryView>
@Instantiated private let loggedInViewBuilder: Instantiator<LoggedInView>
}
@Instantiable
public struct LoggedInView: View, Instantiable {
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
}
@Instantiable
public final class NoteStorage: Instantiable {
// `user` and `stringStorage` are received from ancestors in the tree.
@Received private let user: User
@Received private let stringStorage: StringStorage
}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.
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.
If every dependency can be mocked, calling mock() with no arguments works:
#Preview {
LoggedInView.mock()
}
// Types can give SafeDI a mock without providing a production implementation 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:
#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.
| ✓ 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 |
Three steps to integrate:
- Add
.package(url: "https://github.com/dfed/SafeDI.git", from: "2.0.0")to yourPackage.swiftdependencies. - Attach the
SafeDIGeneratorbuild tool plugin to your first-party target(s). - Decorate your app’s root type with
@Instantiable(isRoot: true)and add@Instantiableto the dependencies it reaches.
Working sample projects live in the Examples folder — clone, open, and build. The Manual 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. If you are upgrading from SafeDI 1.x, follow the 1.x → 2.x migration guide.
SafeDI is closest in spirit to Needle and 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 and 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 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.
I’m glad you’re interested in SafeDI, and I’d love to see where you take it. Please review the contributing guidelines prior to submitting a Pull Request.
Thanks for being part of this journey, and happy injecting!
SafeDI was created by Dan Federman, 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 and has been maintaining the repo since its debut in 2015.
Special thanks to @kierajmumick for helping shape the early design of SafeDI.
