An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# SafeDI

[![CI Status](https://img.shields.io/github/actions/workflow/status/dfed/SafeDI/ci.yml?branch=main)](https://github.com/dfed/SafeDI/actions?query=workflow%3ACI+branch%3Amain)
[![codecov](https://codecov.io/gh/dfed/SafeDI/branch/main/graph/badge.svg)](https://codecov.io/gh/dfed/SafeDI)
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://spdx.org/licenses/MIT.html)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2FSafeDI%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/dfed/SafeDI)
[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2FSafeDI%2Fbadge%3Ftype%3Dplatforms)](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.