{"id":17195529,"url":"https://github.com/dfed/safedi","last_synced_at":"2026-05-02T06:01:58.807Z","repository":{"id":209076303,"uuid":"722378341","full_name":"dfed/SafeDI","owner":"dfed","description":"Compile-time-safe dependency injection in Swift","archived":false,"fork":false,"pushed_at":"2026-04-02T00:00:55.000Z","size":7492,"stargazers_count":179,"open_issues_count":4,"forks_count":8,"subscribers_count":6,"default_branch":"main","last_synced_at":"2026-04-02T01:56:23.322Z","etag":null,"topics":["dependency-injection","dependency-management","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dfed.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"Contributing.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"dfed","buy_me_a_coffee":"dfed","custom":"https://cash.app/$dan"}},"created_at":"2023-11-23T02:41:03.000Z","updated_at":"2026-03-29T17:20:21.000Z","dependencies_parsed_at":"2026-04-06T20:00:38.051Z","dependency_job_id":null,"html_url":"https://github.com/dfed/SafeDI","commit_stats":{"total_commits":279,"total_committers":3,"mean_commits":93.0,"dds":0.007168458781361964,"last_synced_commit":"2a672423560aaf25bc97a1439517c760b5c07ef6"},"previous_names":["dfed/safedi"],"tags_count":103,"template":false,"template_full_name":null,"purl":"pkg:github/dfed/SafeDI","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2FSafeDI","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2FSafeDI/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2FSafeDI/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2FSafeDI/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dfed","download_url":"https://codeload.github.com/dfed/SafeDI/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dfed%2FSafeDI/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31487542,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-06T17:22:55.647Z","status":"ssl_error","status_checked_at":"2026-04-06T17:22:54.741Z","response_time":112,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dependency-injection","dependency-management","swift"],"created_at":"2024-10-15T01:50:42.816Z","updated_at":"2026-05-02T06:01:58.794Z","avatar_url":"https://github.com/dfed.png","language":"Swift","funding_links":["https://github.com/sponsors/dfed","https://buymeacoffee.com/dfed","https://cash.app/$dan"],"categories":["Libs","Recently Updated","Dependency Injection"],"sub_categories":["Dependency Injection","[Who Wants to Be a Millionare](https://www.boardgamecapital.com/who-wants-to-be-a-millionaire-rules.htm)","Getting Started"],"readme":"# SafeDI\n\n[![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)\n[![codecov](https://codecov.io/gh/dfed/SafeDI/branch/main/graph/badge.svg)](https://codecov.io/gh/dfed/SafeDI)\n[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://spdx.org/licenses/MIT.html)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2FSafeDI%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/dfed/SafeDI)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fdfed%2FSafeDI%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/dfed/SafeDI)\n\nCompile-time-safe dependency injection without the boilerplate. No containers. No service locators. No hand-written DI types.\n\n## Why teams choose SafeDI\n\n- **Dependency injection that feels natural.** Get the simplicity of manual dependency injection without ceremony.\n- **Compile-time graph validation.** If the code compiles, the dependency graph is valid.\n- **Scoped runtime values.** Make your real logged-in `User` available non-optionally to any type in the subtree with just a single macro decoration.\n- **Full-graph mocks.** Generated from your real dependency graph, `mock()` lets you override any branch for easy previews and tests.\n- **Architecture-independent.** SwiftUI or UIKit, coordinators or MVVM, one module or hundreds — SafeDI fits what you already have.\n- **Clear failures.** SafeDI flags unsolvable dependency graphs, outlining the problem and suggesting fixes.\n\n## The core concept\n\nSafeDI reads your code, validates your dependencies, and generates production and mock dependency trees—all during project compilation.\n\nOpting 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:\n\n```swift\n// `NotesApp` is the root of the dependency graph. SafeDI generates its `public init()`.\n@Instantiable(isRoot: true) @main\npublic struct NotesApp: App, Instantiable {\n    public init(\n        userService: UserService,\n        nameEntryViewBuilder: Instantiator\u003cNameEntryView\u003e,\n        loggedInViewBuilder: Instantiator\u003cLoggedInView\u003e\n    ) { … }\n\n    public var body: some Scene {\n        WindowGroup {\n            if let user = userService.user {\n                // Forward the authenticated user into the logged-in subtree.\n                loggedInViewBuilder.instantiate(user)\n            } else {\n                nameEntryViewBuilder.instantiate()\n            }\n        }\n    }\n\n    @ObservedObject @Instantiated private var userService: UserService\n    @Instantiated private let nameEntryViewBuilder: Instantiator\u003cNameEntryView\u003e\n    @Instantiated private let loggedInViewBuilder: Instantiator\u003cLoggedInView\u003e\n}\n\n@Instantiable\npublic struct LoggedInView: View, Instantiable {\n    public init(user: User, userService: UserService, noteStorage: NoteStorage) { … }\n\n    public var body: some View { … }\n\n    // `user` is a runtime value forwarded in at this boundary.\n    @Forwarded private let user: User\n    // `userService` is received from an ancestor in the tree.\n    @Received private let userService: UserService\n    // `noteStorage` is created by `LoggedInView` and lives for its lifetime.\n    @Instantiated private let noteStorage: NoteStorage\n}\n```\n\n`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.\n\nThis is the core SafeDI model:\n- write normal Swift types,\n- declare dependencies where they live,\n- let SafeDI validate and generate the wiring.\n\nFor a comprehensive explanation of SafeDI’s macros and their usage, please read [the Macros section of our manual](Documentation/Manual.md#macros).\n\n## Tests and previews from real feature roots\n\nDecorate a type with `@Instantiable(generateMock: true)` and SafeDI generates a `static func mock(…) -\u003e 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.\n\nFor 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:\n\n```swift\n#Preview {\n    LoggedInView.mock()\n}\n\n// Declarations can participate only in mock generation via `mockOnly`.\n@Instantiable(mockOnly: true)\nextension User {\n    public static func mock() -\u003e User {\n        User(name: \"Mock User\")\n    }\n}\n```\n\nFor previews and tests that need real data, pass forwarded values directly and use `safeDIOverrides` to reach into the subtree:\n\n```swift\n#Preview {\n    LoggedInView.mock(\n        user: User(name: \"dfed\"),\n        safeDIOverrides: .init(\n            noteStorage: .init(defaultNote: \"dfed says hello\")\n        )\n    )\n}\n```\n\n`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.\n\n## Features\n\n\u003ctable\u003e\n\u003ctr\u003e\u003ctd\u003e✓ Compile-time safe\u003c/td\u003e\u003ctd\u003e✓ Thread safe\u003c/td\u003e\u003ctd\u003e✓ Hierarchical dependency scoping\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e✓ Constructor injection\u003c/td\u003e\u003ctd\u003e✓ Multi-module support\u003c/td\u003e\u003ctd\u003e✓ Dependency inversion support\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e✓ Transitive dependency solving\u003c/td\u003e\u003ctd\u003e✓ Cycle detection\u003c/td\u003e\u003ctd\u003e✓ Architecture independent\u003c/td\u003e\u003c/tr\u003e\n\u003ctr\u003e\u003ctd\u003e✓ No DI-specific types or generics required\u003c/td\u003e\u003ctd\u003e✓ Full-graph mocks\u003c/td\u003e\u003ctd\u003e✓ Clear errors: never debug generated code\u003c/td\u003e\u003c/tr\u003e\n\u003c/table\u003e\n\n## Getting started\n\nThree steps to integrate:\n\n1. Add `.package(url: \"https://github.com/dfed/SafeDI.git\", from: \"2.0.0\")` to your `Package.swift` dependencies.\n2. Attach the `SafeDIGenerator` build tool plugin to your first-party target(s).\n3. Decorate your app’s root type with `@Instantiable(isRoot: true)` and add `@Instantiable` to the dependencies it reaches.\n\nWorking 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.\n\nIf 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).\n\n## Comparing SafeDI to other DI libraries\n\nSafeDI 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.\n\n[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.\n\nSwiftUI’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.\n\nAcross 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.\n\n## Contributing\n\nI’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.\n\nThanks for being part of this journey, and happy injecting!\n\n## Author\n\nSafeDI 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.\n\nDan 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.\n\n## Acknowledgements\n\nSpecial thanks to [@kierajmumick](http://github.com/kierajmumick) for helping shape the early design of SafeDI.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfed%2Fsafedi","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdfed%2Fsafedi","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdfed%2Fsafedi/lists"}