Skip to content

Commit

Permalink
More documentation polish
Browse files Browse the repository at this point in the history
  • Loading branch information
dfed committed Dec 30, 2024
1 parent 6bba48c commit adec4ff
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 20 deletions.
8 changes: 4 additions & 4 deletions Documentation/Manual.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SafeDI Manual

This manual explains the ins and outs of how to use SafeDI.
This manual provides a detailed guide to using SafeDI effectively in your Swift projects. You’ll learn how to create your dependency tree utilizing SafeDI’s macros, learn recommended approaches to adopting SafeDI, and get a tour of how SafeDI works under the hood.

## Macros

Expand Down Expand Up @@ -57,7 +57,7 @@ public final class UserService: Instantiable {
/// An auth service instance that is instantiated when the `UserService` is instantiated.
@Instantiated private let authService: AuthService

/// An instance of secure, persistent storage that is instantiated further up the SafeDI dependency tree.
/// An instance of secure, persistent storage that is instantiated further up the dependency tree.
@Received private let securePersistentStorage: SecurePersistentStorage

private func loadPersistedUser() -> User? {
Expand Down Expand Up @@ -341,7 +341,7 @@ The `@StateObject` documentation reads:

> Declare state objects as private to prevent setting them from a memberwise initializer, which can conflict with the storage management that SwiftUI provides
`@Instantiated`, `@Forwarded`, or `@Received` objects may be decorated with [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/ObservedObject). Note that `@Instantiated` objects declared on a `View` will be re-initialized when the view is re-initialized. You can find a deep dive on SwiftUI view lifecycles [here](https://www.donnywals.com/understanding-how-and-when-swiftui-decides-to-redraw-views/).
`@Instantiated`, `@Forwarded`, or `@Received` objects may be decorated with [`@ObservedObject`](https://developer.apple.com/documentation/swiftui/ObservedObject). Keep in mind that `@Instantiated` objects in a `View` are re-initialized each time the view is recreated by SwiftUI. You can find a deep dive on SwiftUI view lifecycles [here](https://www.donnywals.com/understanding-how-and-when-swiftui-decides-to-redraw-views/).

### Inheritance

Expand All @@ -357,7 +357,7 @@ We’ve tied everything together with an example multi-user notes application ba

## Under the hood

SafeDI has a `SafeDITool` executable that the `SafeDIGenerator` plugin utilizes to read code and generate a dependency tree. The tool utilizes Apple’s [SwiftSyntax](https://github.com/apple/swift-syntax) library to parse your code and find your `@Instantiable` types’ initializers and dependencies. With this information, SafeDI creates a graph of your project’s dependencies. This graph is validated as part of the `SafeDITool`’s execution, and the tool emits human-readible errors if the dependency graph is not valid. Source code is only generated if the dependency graph is valid.
SafeDI has a `SafeDITool` executable that the `SafeDIGenerator` plugin utilizes to read code and generate a dependency tree. The tool utilizes Apple’s [SwiftSyntax](https://github.com/apple/swift-syntax) library to parse your code and find your `@Instantiable` types’ initializers and dependencies. With this information, SafeDI generates a graph of your project’s dependencies, validates it during `SafeDITool` execution, and provides clear, human-readable error messages if the graph is invalid. Source code is only generated if the dependency graph is valid.

The executable heavily utilizes asynchronous processing to avoid `SafeDITool` becoming a bottleneck in your build. Additionally, we only parse a Swift file with `SwiftSyntax` when the file contains the string `Instantiable`.

Expand Down
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Compile-time-safe dependency injection for Swift projects. SafeDI provides devel

## The core concept

SafeDI reads your code, validates your dependencies, and generates a dependency treeall during project compilation. If your code compiles, your dependency tree is valid.
SafeDI reads your code, validates your dependencies, and generates a dependency treeall during project compilation. If your code compiles, your dependency tree is valid.

Opting a type into the SafeDI dependency tree is straightforward: add the `@Instantiable` macro to your type declaration, and decorate your type’s dependencies with macros to indicate the lifecycle of each property. Here is what a `Boiler` in a `CoffeeMaker` might look like in SafeDI:
Opting a type into the SafeDI dependency tree is simple: add the `@Instantiable` macro to your type declaration, and decorate your type’s dependencies with macros to indicate the lifecycle of each property. Here is what a `Boiler` in a `CoffeeMaker` might look like in SafeDI:

```swift
// The boiler type is opted into SafeDI because it has been decorated with the `@Instantiable` macro.
Expand All @@ -51,7 +51,7 @@ public final class Boiler {


// The boiler createsor in SafeDI parlance ‘instantiates’its pump.
// The boiler createsor in SafeDI parlance ‘instantiates’its pump.
@Instantiated private let pump: Pump
// The boiler receives a reference to a water reservoir that has been instantiated by the coffee maker.
@Received private let waterReservoir: WaterReservoir
Expand All @@ -62,13 +62,13 @@ That is all it takes! SafeDI utilizes macro decorations on your existing types t

## Getting started

SafeDI utilizes both Swift macros and a code generation plugin to read your code and generate a dependency tree. In practice, integrating SafeDI requires three steps:
SafeDI utilizes both Swift macros and a code generation plugin to read your code and generate a dependency tree. To integrate SafeDI, follow these three steps:

1. [Add SafeDI as a dependency to your project](#adding-safedi-as-a-dependency)
1. [Integrate SafeDI’s code generation into your build](#generating-your-safedi-dependency-tree)
1. [Create your dependency tree using SafeDI’s macros](Documentation/Manual.md)

You can see sample integrations in the [Examples](Examples/) folder. If you are migrating an existing project to SafeDI, follow our [migration guide](Documentation/Manual.md#migrating-to-safedi).
You can see sample integrations in the [Examples folder](Examples/). If you are migrating an existing project to SafeDI, follow our [migration guide](Documentation/Manual.md#migrating-to-safedi).

### Adding SafeDI as a Dependency

Expand All @@ -90,7 +90,7 @@ SafeDI provides a code generation plugin named `SafeDIGenerator`. This plugin wo

If your first-party code comprises a single module in an `.xcodeproj`, once your Xcode project depends on the SafeDI package you can integrate the Swift Package Plugin simply by going to your target’s `Build Phases`, expanding the `Run Build Tool Plug-ins` drop-down, and adding the `SafeDIGenerator` as a build tool plug-in. You can see this integration in practice in the [ExampleProjectIntegration](Examples/ExampleProjectIntegration) project.

#### Swift Package
#### Swift package

If your first-party code is entirely contained in a Swift Package with one or more modules, you can add the following lines to your root target’s definition:

Expand Down Expand Up @@ -149,22 +149,27 @@ You can see this in integration in practice in the [ExampleMultiProjectIntegrati

`SafeDITool` can parse all of your Swift files at once, or for even better performance, the tool can be run on each dependent module as part of the build. Running this tool on each dependent module is currently left as an exercise to the reader.

## How SafeDI Compares to Other DI Libraries
## Comparing SafeDI to other DI libraries

SafeDI’s compile-time-safe design makes it similar to [Needle](https://github.com/uber/needle) and [Weaver](https://github.com/scribd/Weaver). Unlike Needle, SafeDI does not require defining dependency protocols for each type that can be instantiated within the DI tree. SafeDI’s capabilities are quite similar to Weaver’s, with the biggest difference being that SafeDI supports codebases with multiple modules, while Weaver does not. Beyond these differences, the choice between SafeDI, Needle, and Weaver largely depends on personal preference.
SafeDI’s compile-time safety and hierarchical dependency scoping make it similar to [Needle](https://github.com/uber/needle) and [Weaver](https://github.com/scribd/Weaver). Unlike Needle, SafeDI does not require defining dependency protocols for each type that can be instantiated within the DI tree. Unlike Weaver, SafeDI does not require defining and maintaining containers that live alongside your regular Swift code.

Other Swift DI libraries like [Swinject](https://github.com/Swinject/Swinject) and [Cleanse](https://github.com/square/Cleanse) do not offer compile-time safety, though other features are similar. A primary benefit of the SafeDI library is that compilation validates your dependency tree.
Other Swift DI libraries, like [Swinject](https://github.com/Swinject/Swinject) and [swift-dependencies](https://github.com/pointfreeco/swift-dependencies), do not offer compile-time safety. Meanwhile, libraries like [Factory](https://github.com/hmlongco/Factory) do offer compile-time validation of the dependency tree, but prevent hierarchical dependency scoping. This means scoped dependencies—like an authentication token in a network layer—can only be optionally injected when using Factory.

Meanwhile, libraries like [Factory](https://github.com/hmlongco/Factory) offer compile-time validation of the dependency tree, but do so by preventing hierarchical dependency scoping, meaning that it’s not possible for scoped systems to have compile-time-safe, non-optional access to scoped dependencies. For example, scoped dependencies–like a session object in a network layer–cannot be safely injected in Factory.
## Contributing

To read about on how SafeDI compares to manual injection, please refer to the [Key Differences section of the manual](Documentation/Manual.md#comparing-safedi-and-manual-injection-key-differences).
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.

## Acknowledgements
Thanks for being part of this journey, and happy injecting!

Special thanks to [@kierajmumick](http://github.com/kierajmumick) for helping shape the early design of SafeDI.
## Author

## Contributing
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.

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.
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.

* [LinkedIn](https://www.linkedin.com/in/dsfed)
* [BlueSky](https://bsky.app/profile/dfed.me)

Thanks, and happy injecting!
## Acknowledgements

Special thanks to [@kierajmumick](http://github.com/kierajmumick) for helping shape the early design of SafeDI.

0 comments on commit adec4ff

Please sign in to comment.