From 234a3792edfc6ea430ca856f21b103032d796e5b Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 18 Dec 2023 20:02:16 -0800 Subject: [PATCH 01/18] Write README.md --- .github/workflows/ci.yml | 9 + README.md | 418 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 426 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d49caccb..6d59e11e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -44,3 +44,12 @@ jobs: run: sudo xcode-select --switch /Applications/Xcode_15.0.1.app/Contents/Developer - name: Build Project Integration run: pushd Examples/ExampleProjectIntegration; xcrun xcodebuild build -skipPackagePluginValidation -skipMacroValidation -scheme ExampleProjectIntegration; popd + + readme-validation: + name: Check Markdown links + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v3 + - name: Validate Markdown + uses: gaurav-nelson/github-action-markdown-link-check@v1 diff --git a/README.md b/README.md index 08f0fa02..7ad1627c 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,418 @@ # SafeDI -Compile-time safe dependency injection in Swift + +[![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) +[![Swift Package Manager compatible](https://img.shields.io/badge/SPM-✓-4BC51D.svg?style=flat)](https://github.com/apple/swift-package-manager) +[![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) + +Compile-time safe dependency injection for Swift projects. + +## Features + +✅ Compile-time safe + +✅ Thread safe + +✅ Hierarchical dependency scoping + +✅ Constructor injection + +✅ Multi-module support + +✅ Dependency inversion support + +✅ Transitive dependency solving + +✅ Cycle detection + +✅ Architecture independent + +✅ Simple integration: no DI-specific types or generics required + +✅ Easy testing: every type has a memberwise initializer + +✅ Clear error messages: never debug generated code + +## Using SafeDI + +SafeDI utilizes Swift Macros and build plugins to read your code and generate a dependency tree that is validated at compile time. Dependencies can either be instantiated by SafeDI or forwarded into the SafeDI dependency tree. + +SafeDI relies on Swift Macros to opt types and their properties into the dependency injection (DI) system. SafeDI macros decorate types or extensions to mark the decorated type as being capable of being instantiated by SafeDI, and decorate properties of `@Instantiable`-decorated types to mark them as being part of the SafeDI dependency hierarchy. + +### @Instantiable + +Type declarations decorated with the [`@Instantiable` macro](Sources/SafeDI/PropertyDecoration/Instantiable.swift) are able to be instantiated by SafeDI. Types decorated with this macro can instantiate other `@Instantiable` dependencies, forward dependencies injected from outside of SafeDI, or receive dependencies instantiated or forwarded by objects further up the dependency tree. + +Every `@Instantiable`-decorated type must be: + +1. `public` or `open` + +2. Have a `public init(…)` or `open init(…)` method that receives every injectable property + +The `@Instantiable` guides engineers through satisfying these requirements with build-time FixIts. + +#### Example + +Here is a sample `UserService` implementation that is `@Instantiable`: + +```swift +import SafeDI + +/// A protocol that defines a UserService. +/// It is not necessary to utilize protocols with SafeDI, but since +/// protocol-driven development aids both testability and dependency +/// inversion, our examples show how protocols can be used with SafeDI. +public protocol UserService { + var user: User? { get } + func login(username: String, password: String) async throws -> User +} + +/// A default implementation of `UserService` that can fulfill `@Instantiated` +/// properties of type `UserService` or `DefaultUserService`. +@Instantiable(fulfillingAdditionalTypes: [UserService.self]) +public final class DefaultUserService: UserService { + + // MARK: Initialization + + /// Public, memberwise initializer that takes each injected property. + public init(authService: AuthService, securePersistentStorage: SecurePersistentStorage) { + self.authService = authService + self.securePersistentStorage = securePersistentStorage + } + + // MARK: UserService + + public private(set) lazy var user: User? = loadPersistedUser() { + didSet { + persistUserData() + } + } + + public func login(username: String, password: String) async throws -> User { + let user = try await authService.login(username: username, password: password) + self.user = user + return user + } + + // MARK: Private + + /// An auth service instance that is instantiated when the `DefaultUserService` is instantiated. + @Instantiated + private let authService: AuthService + + /// An instance of secure, persistent storage that is instantiated further up the SafeDI dependency tree. + @Received + private let securePersistentStorage: SecurePersistentStorage + + private func loadPersistedUser() -> User? { + securePersistentStorage["user", ofType: User.self] + } + + private func persistUserData() { + securePersistentStorage["user"] = user + } +} +``` + +### @ExternalInstantiable + +Types that are declared outside of your project can be instantiated by SafeDI if there is an extension on the type decorated with the [`@ExternalInstantiable` macro](Sources/SafeDI/PropertyDecoration/ExternalInstantiable.swift). Extensions decorated with this macro define how to instantiate the extended type via a `public func instantiate(…) -> ExtendedType` method. This `instantiate(…)` method can receive dependencies instantiated or forwarded by objects further up the dependency tree by declaring these dependencies as method arguments. + +#### Example + +Here is a sample `SecurePersistentStorage` protocol whose concrete type is defined in a third-party dependency, and therefore is `@ExternalInstantiable`: + +```swift +import Foundation +import SafeDI +import Valet // A Keychain wrapper that can be used to implement secure, persistent storage. github.com/square/valet + +/// A protocol defining how to interact with secure, persistent storage. +/// It is not necessary to utilize protocols with SafeDI, but since +/// protocol-driven development aids both testability and dependency +/// inversion, our examples show how protocols can be used with SafeDI. +protocol SecurePersistentStorage { + subscript(_ key: String, ofType type: CodableType.Type) -> CodableType? { get } + subscript(_ key: String) -> CodableType? { get set } +} + +/// A default implementation of `SecurePersistentStorage` that can fulfill +/// `@Instantiated` properties of type `Valet` or `SecurePersistentStorage`. +@ExternalInstantiable(fulfillingAdditionalTypes: [SecurePersistentStorage.self]) +extension Valet: SecurePersistentStorage { + + /// A public initializer defines how SafeDI can instantiate a Valet object. + public static func instantiate() -> Valet { + Valet.valet( + with: Identifier(nonEmpty: "SafeDIExample")!, + accessibility: .afterFirstUnlock + ) + } + + subscript(_ key: String, ofType type: CodableType.Type) -> CodableType? { + guard let data = try? object(forKey: key) else { return nil } + return try? JSONDecoder().decode(type, from: data) + } + + subscript(key: String) -> CodableType? { + get { + self[key, ofType: CodableType.self] + } + set { + if let newValue, let data = try? JSONEncoder().encode(newValue) { + try? setObject(data, forKey: key) + } else { + try? removeObject(forKey: key) + } + } + } +} +``` + +### @Instantiated + +Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Instantiated` macro](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be [`@Received`](#received) by objects instantiated further down the dependency tree. + +`@Instantiated`-decorated properties must declared as an `@Instantiable` type, or of an `additionalType` listed in a `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. + +#### Utilizing @Instantiated with type erased properties + +When you want to instantiate a type-erased property, you may specify which concrete type you expect to fulfill your property by utilizing `@Instantiated`‘s `fulfilledByType` parameter. + +```swift +import SwiftUI + +@Instantiable +public struct ParentView: View { + public var body: some View { + VStack { + Text("Child View") + childViewBuilder.instantiate() + } + } + + public init(childViewBuilder: Instantiator) { + self.childViewBuilder = childViewBuilder + } + + // The Instantiator‘s `instantiate()` method will build a view of type `ChildView`. + // Because the type is passed in as a string literal, this code does not need to + // have a dependency on the module that defines `ChildView`. All that is required + // for this code to compile is for there to be an + // `@Instantiable public struct ChildView: View` in the codebase. + @Instantiated(fulfilledByType: "ChildView") + private let childViewBuilder: Instantiator +} +``` + +### @Forwarded + +Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Forwarded` macro](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` method. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. + +A single `@Instantiable` type may have at most one `@Forwarded`-decorated property. + +### @Received + +Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Received` macro](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. + +### Delayed instantiation + +When you want to instantiate a dependency after your `init(…)`, you need to declare an `Instantiator`-typed property as `@Instantiated` or `@Received`. + +#### Instantiator + +The `Instantiator` type is how SafeDI enables deferred instantiation of an `@Instantiable` type. `Instantiator` has a single generic that matches the type of the to-be-instantiated instance. Creating an `Instantiator` property is as simple as creating any other property in the SafeDI ecosystem: + +```swift +@Instantiable +public struct MyApp: App { + public var body: some Scene { + WindowGroup { + // Returns a new instance of a `ContentView`. + contentViewInstantiator.instantiate() + } + } + + public init(contentViewInstantiator: Instantiator) { + self.contentViewInstantiator = contentViewInstantiator + } + + /// A private property that knows how to instantiate a content view. + @Instantiated + private let contentViewInstantiator: Instantiator +} +``` + +It is possible to write a `Instantiator` with a type-erased generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. + +#### ForwardingInstantiator + +The `ForwardingInstantiator` type is how SafeDI enables instantiating any `@Instantiable` type with a `@Forwarded` property. `ForwardingInstantiator` has two generics. The first generic must match the type of the `@Forwarded` property. The second generic matches the type of the to-be-instantiated instance. + +```swift +@Instantiable +public struct MyApp: App { + public var body: some Scene { + WindowGroup { + if let user = userService.user { + // Returns a new instance of a `LoggedInContentView`. + loggedInContentViewInstantiator.instantiate(user) + } else { + // Returns a new instance of a `LoggedOutContentView`. + loggedOutContentViewInstantiator.instantiate() + } + } + } + + public init(loggedInContentViewInstantiator: ForwardingInstantiator, loggedOutContentViewInstantiator: Instantiator, userService: UserService) { + self.loggedInContentViewInstantiator = loggedInContentViewInstantiator + self.loggedOutContentViewInstantiator = loggedOutContentViewInstantiator + self.userService = userService + } + + /// A private property that knows how to instantiate a logged-in content view. + @Instantiated + private let loggedInContentViewInstantiator: ForwardingInstantiator + + /// A private property that knows how to instantiate a logged-out content view. + @Instantiated + private let loggedOutContentViewInstantiator: Instantiator + + @ObservedObject + @Instantiated + private let userService: UserService +} +``` + +It is possible to write a `ForwardingInstantiator` with a type-erased second generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. + +### Creating the root of your dependency tree + +SafeDI automatically finds the root(s) of your dependency tree, and creates an extension on each root that contains a `public init()` method that instantiates the dependency tree. + +An `@Instantiable` type qualifies as the root of a dependency tree if and only if: + +1. The type‘s SafeDI-injected properties are all `@Instantiated` +2. The type is not instantiated by another `@Instantiable` type + +### Comparing SafeDI and Manual Injection: Key Differences + +SafeDI is designed to be simple to adopt and minimize architectural changes required to get the benefits of a compile-time safe DI system. Despite this design goal, there are a few key differences between projects that utilize SafeDI and projects that don‘t. As the benefits of this system are clearly outlined in the [Features](#features) section above, this section outlines the pattern changes required to utilize a DI system like SafeDI. + +#### Instantiating objects + +In a manual DI system, it is common to directly call your dependencies‘ `init(…)` methods. When utilizing SafeDI, you must rely on `@Instantiated`-decorated properties to instantiate your dependencies for you. Calling a dependency‘s `init(…)` method directly effectively exits the SafeDI-built dependency tree. + +To instantiate a dependency after a property‘s enclosing type is initialized, you must utilize an instantiated or received `Instantiator` or `ForwardingInstantiator` instance. + +#### SwiftUI + +It is important to avoid decorating initializer-injected dependencies with the [`@State`](https://developer.apple.com/documentation/swiftui/state) and [`@StateObject`](https://developer.apple.com/documentation/swiftui/stateobject) property wrappers. Apple‘s documentation for both of these property wrappers makes it clear. + +The `@State` documentation reads: + +> Declare state as private to prevent setting it in a memberwise initializer, which can conflict with the storage management that SwiftUI provides + +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) instead of `@StateObject`. Note that `@Instantiated` objects will be re-initialized when a view‘s parent view is invalidated. + +### Migrating to SafeDI + +It is strongly recommended that projects adopting SafeDI start their migration by identifying the root of their dependency tree and making it `@Instantiable`. Once your root object has adopted SafeDI, continue migrating dependencies to SafeDI in either a breadth-first or depth-first manner. As your adoption of SafeDI progression, you‘ll find that you are removing more code than you are adding: many of your dependencies are likely being passed through intermediary objects that do not utilize the dependency except to instantiate a dependency deeper in the tree. Once types further down the dependency tree have adopted SafeDI, you will be able to avoid receiving dependencies in intermediary types. + +## Example App + +We‘ve tied everything together with an example multi-user notes application backed by SwiftUI. You compile and run this code in Xcode in the included [ExampleProjectIntegration](Examples/ExampleProjectIntegration) project. + +## Integrating SafeDI + +To integrate SafeDI into your codebase, you must both depend on the SafeDI framework that defines the core macros and instantiators, and also add the code generation step to your build process. You can see sample integrations in the [Examples](Examples/) folder. + +### SafeDI Framework + +To install the SafeDI framework into your package with [Swift Package Manager](https://github.com/apple/swift-package-manager), add the following lines to your `Package.swift` file: + +```swift +dependencies: [ + .package(url: "https://github.com/dfed/SafeDI", from: "0.1.0"), +] +``` + +To install the SafeDI framework into an Xcode project with Swift Package Manager, follow [Apple‘s instructions](https://developer.apple.com/documentation/xcode/adding-package-dependencies-to-your-app) to add the `https://github.com/dfed/SafeDI` dependency to your application. + +### SafeDI Code Generation + +SafeDI vends a code generation plugin named `SafeDIGenerator`. This plugin works out of the box on a limited number of project configurations. If your project does not fall into these well-supported configurations, you can configure your build to utilize the `SafeDITool` command-line executable directly. + +#### Single-module Xcode projects + +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 + +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: + +```swift + plugins: [ + .plugin(name: "SafeDIGenerator", package: "SafeDI") + ] +``` + +You can see this in integration in practice in the [ExamplePackageIntegration](Examples/ExamplePackageIntegration) package. + +#### Other configurations + +If your first-party code comprises multiple modules in Xcode, or a mix of Xcode Projects and Swift Packages, or some other configuration not listed above, you will need to utilize the `SafeDITool` command-line executable directly. + +The `SafeDITool` utility is designed to able to be integrated into projects of any size or shape. + +You can build the SafeDI tool locally by running the following command at the root of this repository: +```zsh +swift build -c release +``` + +Once you‘ve built the tool, you can see the tool‘s expected arguments by running: +```zsh +$(swift build -c release --target SafeDITool --show-bin-path)/SafeDITool --help +``` + +The `SafeDITool` can parse all of your Swift files at once, or for better performance the tool can be run on each dependent module as part of the build. Integrating this tool into your project is currently left as an exercise to the reader. SafeDI would welcome a better documented approach or tool for integrating SafeDI into currently unsupported projects. + +### Requirements + +* Xcode 15.0 or later +* iOS 13 or later +* tvOS 13 or later +* watchOS 6 or later +* macOS 10.13 or later + +## 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 directed, acyclic 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. + +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`. + +Due to limitations in Apple‘s [Swift Package Manager Plugins](https://github.com/apple/swift-package-manager/blob/main/Documentation/Plugins.md), the `SafeDIGenerator` plugin parses all of your first-party Swift files in a single pass. Projects that utilize `SafeDITool` directly can process Swift files on a per-module basis to further reduce the build-time bottleneck. + +## 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 DI-tree-instantiable type. SafeDI‘s capabilities are quite similar to Weaver‘s, with the biggest difference being that SafeDI supports codebases with multiple modules. Beyond those differences, SafeDI vs Needle vs Weaver is a matter of personal preference. + +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. + +## Acknowledgements + +Huge thanks to @kierajmumick for helping hone the early design of SafeDI. + +## Developing + +Double-click on the `Package.swift` file in the root of the repository to open the package in Xcode. + +## Contributing + +I’m glad you’re interested in SafeDI, and we‘d love to see where you take it. Please read the [contributing guidelines](Contributing.md) prior to submitting a Pull Request. + +Thanks, and happy injecting! From ea25e618df5fc1a970a6602d98857b7d2cd0fec1 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 18 Dec 2023 22:06:18 -0800 Subject: [PATCH 02/18] @ObservedObject-decorated properties must be 'var' not 'let' --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7ad1627c..67197654 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ public struct MyApp: App { @ObservedObject @Instantiated - private let userService: UserService + private var userService: UserService } ``` From dcca6f539d0cc6892836436d4433158e5be6cc34 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 18 Dec 2023 22:08:23 -0800 Subject: [PATCH 03/18] Link to kierajmumick --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 67197654..ebadb548 100644 --- a/README.md +++ b/README.md @@ -405,7 +405,7 @@ Other Swift DI libraries like [Swinject](https://github.com/Swinject/Swinject) a ## Acknowledgements -Huge thanks to @kierajmumick for helping hone the early design of SafeDI. +Huge thanks to [@kierajmumick](http://github.com/kierajmumick) for helping hone the early design of SafeDI. ## Developing From 8df7d39978371d9069e23ae674d5304cb4a68e62 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 18 Dec 2023 23:51:09 -0800 Subject: [PATCH 04/18] whitespace --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index ebadb548..ecd922c1 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Every `@Instantiable`-decorated type must be: 2. Have a `public init(…)` or `open init(…)` method that receives every injectable property -The `@Instantiable` guides engineers through satisfying these requirements with build-time FixIts. +The `@Instantiable` guides engineers through satisfying these requirements with build-time FixIts. #### Example @@ -100,7 +100,7 @@ public final class DefaultUserService: UserService { @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 SafeDI dependency tree. @Received private let securePersistentStorage: SecurePersistentStorage @@ -207,13 +207,13 @@ public struct ParentView: View { ### @Forwarded -Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Forwarded` macro](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` method. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. +Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Forwarded` macro](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` method. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. A single `@Instantiable` type may have at most one `@Forwarded`-decorated property. ### @Received -Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Received` macro](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. +Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Received` macro](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. ### Delayed instantiation @@ -243,7 +243,7 @@ public struct MyApp: App { } ``` -It is possible to write a `Instantiator` with a type-erased generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. +It is possible to write a `Instantiator` with a type-erased generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. #### ForwardingInstantiator @@ -284,7 +284,7 @@ public struct MyApp: App { } ``` -It is possible to write a `ForwardingInstantiator` with a type-erased second generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. +It is possible to write a `ForwardingInstantiator` with a type-erased second generic by utilizing `@Instantiated`‘s `fulfilledByType` parameter. ### Creating the root of your dependency tree @@ -297,7 +297,7 @@ An `@Instantiable` type qualifies as the root of a dependency tree if and only i ### Comparing SafeDI and Manual Injection: Key Differences -SafeDI is designed to be simple to adopt and minimize architectural changes required to get the benefits of a compile-time safe DI system. Despite this design goal, there are a few key differences between projects that utilize SafeDI and projects that don‘t. As the benefits of this system are clearly outlined in the [Features](#features) section above, this section outlines the pattern changes required to utilize a DI system like SafeDI. +SafeDI is designed to be simple to adopt and minimize architectural changes required to get the benefits of a compile-time safe DI system. Despite this design goal, there are a few key differences between projects that utilize SafeDI and projects that don‘t. As the benefits of this system are clearly outlined in the [Features](#features) section above, this section outlines the pattern changes required to utilize a DI system like SafeDI. #### Instantiating objects From 8630764ecea1b1b20c8440e3db9f96fb83336a67 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 18 Dec 2023 23:51:06 -0800 Subject: [PATCH 05/18] Better introductory overview --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ecd922c1..f8a54c7a 100644 --- a/README.md +++ b/README.md @@ -35,9 +35,13 @@ Compile-time safe dependency injection for Swift projects. ## Using SafeDI -SafeDI utilizes Swift Macros and build plugins to read your code and generate a dependency tree that is validated at compile time. Dependencies can either be instantiated by SafeDI or forwarded into the SafeDI dependency tree. +SafeDI utilizes Swift Macros and Swift Package Manager plugins to read your code and generate a dependency tree that is validated at compile time. Dependencies can either be instantiated by SafeDI or forwarded into the SafeDI dependency tree. -SafeDI relies on Swift Macros to opt types and their properties into the dependency injection (DI) system. SafeDI macros decorate types or extensions to mark the decorated type as being capable of being instantiated by SafeDI, and decorate properties of `@Instantiable`-decorated types to mark them as being part of the SafeDI dependency hierarchy. +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 that signal the lifecycle of each property. Decorate a property with `@Instantiated` if you want to initialize it when the enclosing type is initialized; `@Forwarded` if you want to propagate a runtime-determine value down the dependency tree; or `@Received` if you want to receive the property from an `@Instantiated` or `@Forwarded` property further up the dependency tree. + +If a type is declared in third-party code, you can declare an extension the type in your code and decorate it with the `@ExternalInstantiable` macro to opt it into SafeDI. + +Let‘s walk through each of these macros in detail. ### @Instantiable From 92c3f030f21f567523e817aa880f259d946cd90a Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Wed, 20 Dec 2023 19:11:18 -0800 Subject: [PATCH 06/18] Pull info out of sample code --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f8a54c7a..c2151f32 100644 --- a/README.md +++ b/README.md @@ -183,6 +183,8 @@ Property declarations within [`@Instantiable`](#instantiable) types decorated wi When you want to instantiate a type-erased property, you may specify which concrete type you expect to fulfill your property by utilizing `@Instantiated`‘s `fulfilledByType` parameter. +The `fulfilledByType` parameter takes a `String` of the name of the concrete type that will be assigned to the type-erased property. Representing the type as a string literal allows for dependency inversion: the code that receives the concrete type does not need to have a dependency on the module that defines the concrete type. + ```swift import SwiftUI From 03079a5153035bbf736d2e9bdef6f6fb565e549f Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 23 Dec 2023 12:25:40 -0800 Subject: [PATCH 07/18] Review feedback --- README.md | 138 +++++++++++++++++++++++++----------------------------- 1 file changed, 64 insertions(+), 74 deletions(-) diff --git a/README.md b/README.md index c2151f32..7f235805 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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) -Compile-time safe dependency injection for Swift projects. +Compile-time safe dependency injection for Swift projects. SafeDI is built for engineers who crave the safety and simplicity of manual dependency injection without the overhead of boilerplate code. ## Features @@ -37,44 +37,36 @@ Compile-time safe dependency injection for Swift projects. SafeDI utilizes Swift Macros and Swift Package Manager plugins to read your code and generate a dependency tree that is validated at compile time. Dependencies can either be instantiated by SafeDI or forwarded into the SafeDI dependency tree. -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 that signal the lifecycle of each property. Decorate a property with `@Instantiated` if you want to initialize it when the enclosing type is initialized; `@Forwarded` if you want to propagate a runtime-determine value down the dependency tree; or `@Received` if you want to receive the property from an `@Instantiated` or `@Forwarded` property further up the dependency tree. +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 that signal the lifecycle of each property. There are just three macros that decorate properties: -If a type is declared in third-party code, you can declare an extension the type in your code and decorate it with the `@ExternalInstantiable` macro to opt it into SafeDI. +* `@Instantiated`: instantiates an instance or value when the enclosing type is instantiated +* `@Forwarded`: propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree +* `@Received`: receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. + +If a type is declared in third-party code, you can declare an extension of the type in your code and decorate it with the same `@Instantiable` macro to opt it into SafeDI. Let‘s walk through each of these macros in detail. ### @Instantiable -Type declarations decorated with the [`@Instantiable` macro](Sources/SafeDI/PropertyDecoration/Instantiable.swift) are able to be instantiated by SafeDI. Types decorated with this macro can instantiate other `@Instantiable` dependencies, forward dependencies injected from outside of SafeDI, or receive dependencies instantiated or forwarded by objects further up the dependency tree. +Type declarations decorated with [`@Instantiable`](Sources/SafeDI/PropertyDecoration/Instantiable.swift) are able to be instantiated by SafeDI. Types decorated with this macro can instantiate other `@Instantiable` dependencies, forward dependencies injected from outside of SafeDI, or receive dependencies instantiated or forwarded by objects further up the dependency tree. + +SafeDI is designed to make instantiating and receiving dependencies _simple_, without requiring engineers to think about abstract dependency injection (DI) concepts. That said, for those familiar with other DI systems: each `@Instantiable` type is its own [Scope](https://medium.com/@aarontharris/scope-dependency-injection-6fc25beffc9c). For those unfamiliar with DI terminology, know that each `@Instantiable` type is responsible for retaining its dependencies, and that every `@Instantiated` or `@Forwarded` dependency is available to all transitive child dependencies. Every `@Instantiable`-decorated type must be: 1. `public` or `open` -2. Have a `public init(…)` or `open init(…)` method that receives every injectable property +2. Have a `public init(…)` or `open init(…)` that receives every injectable property The `@Instantiable` guides engineers through satisfying these requirements with build-time FixIts. -#### Example - Here is a sample `UserService` implementation that is `@Instantiable`: ```swift import SafeDI -/// A protocol that defines a UserService. -/// It is not necessary to utilize protocols with SafeDI, but since -/// protocol-driven development aids both testability and dependency -/// inversion, our examples show how protocols can be used with SafeDI. -public protocol UserService { - var user: User? { get } - func login(username: String, password: String) async throws -> User -} - -/// A default implementation of `UserService` that can fulfill `@Instantiated` -/// properties of type `UserService` or `DefaultUserService`. -@Instantiable(fulfillingAdditionalTypes: [UserService.self]) -public final class DefaultUserService: UserService { +public final class UserService { // MARK: Initialization @@ -118,66 +110,52 @@ public final class DefaultUserService: UserService { } ``` -### @ExternalInstantiable - -Types that are declared outside of your project can be instantiated by SafeDI if there is an extension on the type decorated with the [`@ExternalInstantiable` macro](Sources/SafeDI/PropertyDecoration/ExternalInstantiable.swift). Extensions decorated with this macro define how to instantiate the extended type via a `public func instantiate(…) -> ExtendedType` method. This `instantiate(…)` method can receive dependencies instantiated or forwarded by objects further up the dependency tree by declaring these dependencies as method arguments. +#### Making protocols `@Instantiable` -#### Example - -Here is a sample `SecurePersistentStorage` protocol whose concrete type is defined in a third-party dependency, and therefore is `@ExternalInstantiable`: +While it is not necessary to utilize protocols with SafeDI, protocol-driven development aids both testability and dependency inversion. The `@Instantiable` macro has a parameter `fulfillingAdditionalTypes` that enables any concrete `@Instantiable` type to fulfill properties that are declared as conforming to a protocol (or superclass) type. Here’s a sample implementation of a protocol-backed, `@Instantiable` `UserService`: ```swift -import Foundation import SafeDI -import Valet // A Keychain wrapper that can be used to implement secure, persistent storage. github.com/square/valet - -/// A protocol defining how to interact with secure, persistent storage. -/// It is not necessary to utilize protocols with SafeDI, but since -/// protocol-driven development aids both testability and dependency -/// inversion, our examples show how protocols can be used with SafeDI. -protocol SecurePersistentStorage { - subscript(_ key: String, ofType type: CodableType.Type) -> CodableType? { get } - subscript(_ key: String) -> CodableType? { get set } + +/// A protocol that defines a UserService. +public protocol UserService { + var user: User? { get } + func login(username: String, password: String) async throws -> User } -/// A default implementation of `SecurePersistentStorage` that can fulfill -/// `@Instantiated` properties of type `Valet` or `SecurePersistentStorage`. -@ExternalInstantiable(fulfillingAdditionalTypes: [SecurePersistentStorage.self]) -extension Valet: SecurePersistentStorage { - - /// A public initializer defines how SafeDI can instantiate a Valet object. - public static func instantiate() -> Valet { - Valet.valet( - with: Identifier(nonEmpty: "SafeDIExample")!, - accessibility: .afterFirstUnlock - ) - } +/// A default implementation of `UserService` that can fulfill `@Instantiated` +/// properties of type `UserService` or `DefaultUserService`. +@Instantiable(fulfillingAdditionalTypes: [UserService.self]) +public final class DefaultUserService: UserService { + ... // Same implementation as above. +} +``` - subscript(_ key: String, ofType type: CodableType.Type) -> CodableType? { - guard let data = try? object(forKey: key) else { return nil } - return try? JSONDecoder().decode(type, from: data) - } +#### Making external types `@Instantiable` - subscript(key: String) -> CodableType? { - get { - self[key, ofType: CodableType.self] - } - set { - if let newValue, let data = try? JSONEncoder().encode(newValue) { - try? setObject(data, forKey: key) - } else { - try? removeObject(forKey: key) - } - } +Types that are declared outside of your project can be instantiated by SafeDI if there is an extension on the type decorated with the `@Instantiable` macro. Extensions decorated with this macro define how to instantiate the extended type via a `public func instantiate(…) -> ExtendedType` function. This `instantiate(…)` function can receive dependencies instantiated or forwarded by objects further up the dependency tree by declaring these dependencies as arguments to the `instantiate(…)` function. + +Here we have a sample `@Instantiable` `SecurePersistentStorage` whose concrete type is defined in a third-party dependency: + +```swift +import Foundation +import SafeDI +import SecurePersistentStorage // A third-party library that provides secure, persistent storage. + +@Instantiable +extension SecurePersistentStorage { + /// A public static function that defines how SafeDI can instantiate the type. + public static func instantiate() -> SecurePersistentStorage { + SecurePersistentStorage() } } ``` ### @Instantiated -Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Instantiated` macro](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be [`@Received`](#received) by objects instantiated further down the dependency tree. +Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Instantiated`](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be [`@Received`](#received) by objects instantiated further down the dependency tree. -`@Instantiated`-decorated properties must declared as an `@Instantiable` type, or of an `additionalType` listed in a `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. +`@Instantiated`-decorated properties must be declared as an `@Instantiable` type, or of an `additionalType` listed in a `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. #### Utilizing @Instantiated with type erased properties @@ -201,7 +179,7 @@ public struct ParentView: View { self.childViewBuilder = childViewBuilder } - // The Instantiator‘s `instantiate()` method will build a view of type `ChildView`. + // The Instantiator‘s `instantiate()` function will build a view of type `ChildView`. // Because the type is passed in as a string literal, this code does not need to // have a dependency on the module that defines `ChildView`. All that is required // for this code to compile is for there to be an @@ -213,21 +191,32 @@ public struct ParentView: View { ### @Forwarded -Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Forwarded` macro](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` method. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. +Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` function. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. + +`@Forwarded` enables injecting runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` properties are unlikely to conform to types decorated with the `@Instantiable` macro. -A single `@Instantiable` type may have at most one `@Forwarded`-decorated property. +A single `@Instantiable` type may have at most one `@Forwarded`-decorated property. `@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. ### @Received -Property declarations within [`@Instantiable`](#instantiable) types decorated with the [`@Received` macro](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. +Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Received`](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. + +### Macro cheat sheet + +| Macro | Decorating | Usage | +| ------ | ----------- | ----- | +| `@Instantiable` | Type declaration | Makes a type capable of being instantiated by SafeDI. | +| `@Instantiated` | Property declaration | Instantiates an instance or value when the enclosing type is instantiated. | +| `@Forwarded` | Property declaration | Propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree. | +| `@Received` | Property declaration | Receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. | ### Delayed instantiation -When you want to instantiate a dependency after your `init(…)`, you need to declare an `Instantiator`-typed property as `@Instantiated` or `@Received`. +When you want to instantiate a dependency after `init(…)`, you need to declare an `Instantiator`-typed property as `@Instantiated` or `@Received`. #### Instantiator -The `Instantiator` type is how SafeDI enables deferred instantiation of an `@Instantiable` type. `Instantiator` has a single generic that matches the type of the to-be-instantiated instance. Creating an `Instantiator` property is as simple as creating any other property in the SafeDI ecosystem: +The `Instantiator` type is how SafeDI enables deferred instantiation of an `@Instantiable` type that has no `@Forwarded`-decorated properties. `Instantiator` has a single generic that matches the type of the to-be-instantiated instance. Creating an `Instantiator` property is as simple as creating any other property in the SafeDI ecosystem: ```swift @Instantiable @@ -253,7 +242,7 @@ It is possible to write a `Instantiator` with a type-erased generic by utilizing #### ForwardingInstantiator -The `ForwardingInstantiator` type is how SafeDI enables instantiating any `@Instantiable` type with a `@Forwarded` property. `ForwardingInstantiator` has two generics. The first generic must match the type of the `@Forwarded` property. The second generic matches the type of the to-be-instantiated instance. +The `ForwardingInstantiator` type is how SafeDI enables instantiating any `@Instantiable` type with a `@Forwarded`-decorated property. `ForwardingInstantiator` has two generics. The first generic must match the type of the `@Forwarded`-decorated property. The second generic matches the type of the to-be-instantiated instance. ```swift @Instantiable @@ -284,6 +273,7 @@ public struct MyApp: App { @Instantiated private let loggedOutContentViewInstantiator: Instantiator + /// An instance of an observable `UserService` that is instantiated when `MyApp` is instantiated. @ObservedObject @Instantiated private var userService: UserService @@ -294,7 +284,7 @@ It is possible to write a `ForwardingInstantiator` with a type-erased second gen ### Creating the root of your dependency tree -SafeDI automatically finds the root(s) of your dependency tree, and creates an extension on each root that contains a `public init()` method that instantiates the dependency tree. +SafeDI automatically finds the root(s) of your dependency tree, and creates an extension on each root that contains a `public init()` function that instantiates the dependency tree. An `@Instantiable` type qualifies as the root of a dependency tree if and only if: @@ -307,7 +297,7 @@ SafeDI is designed to be simple to adopt and minimize architectural changes requ #### Instantiating objects -In a manual DI system, it is common to directly call your dependencies‘ `init(…)` methods. When utilizing SafeDI, you must rely on `@Instantiated`-decorated properties to instantiate your dependencies for you. Calling a dependency‘s `init(…)` method directly effectively exits the SafeDI-built dependency tree. +In a manual DI system, it is common to directly call your dependencies‘ `init(…)` functions. When utilizing SafeDI, you must rely on `@Instantiated`-decorated properties to instantiate your dependencies for you. Calling a dependency‘s `init(…)` function directly effectively exits the SafeDI-built dependency tree, which removes property lifecycle guarantees. To instantiate a dependency after a property‘s enclosing type is initialized, you must utilize an instantiated or received `Instantiator` or `ForwardingInstantiator` instance. From c1494e1e9148c92ff50dbe483e5fd53bbab4024a Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 23 Dec 2023 16:48:23 -0800 Subject: [PATCH 08/18] Reduce repetition and address more feedback --- README.md | 34 ++++++++++++++-------------------- 1 file changed, 14 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 7f235805..262685e5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![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) -Compile-time safe dependency injection for Swift projects. SafeDI is built for engineers who crave the safety and simplicity of manual dependency injection without the overhead of boilerplate code. +Compile-time safe dependency injection for Swift projects. SafeDI is built for engineers who want the safety and simplicity of manual dependency injection without the overhead of boilerplate code. ## Features @@ -37,13 +37,16 @@ Compile-time safe dependency injection for Swift projects. SafeDI is built for e SafeDI utilizes Swift Macros and Swift Package Manager plugins to read your code and generate a dependency tree that is validated at compile time. Dependencies can either be instantiated by SafeDI or forwarded into the SafeDI dependency tree. -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 that signal the lifecycle of each property. There are just three macros that decorate properties: +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 that signal the lifecycle of each property. If a type is declared in third-party code, you can opt it into SafeDI by declaring an extension of the type in your code and decorating it with the same `@Instantiable` macro. -* `@Instantiated`: instantiates an instance or value when the enclosing type is instantiated -* `@Forwarded`: propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree -* `@Received`: receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. +There are a total of four macros in the SafeDI library: -If a type is declared in third-party code, you can declare an extension of the type in your code and decorate it with the same `@Instantiable` macro to opt it into SafeDI. +| Macro | Decorating | Usage | +| ------ | ----------- | ----- | +| `@Instantiable` | Type or extension declaration | Makes a type capable of being instantiated by SafeDI. | +| `@Instantiated` | Property declaration | Instantiates an instance or value when the enclosing `@Instantiable`-decorated type is instantiated. | +| `@Forwarded` | Property declaration | Propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree. | +| `@Received` | Property declaration | Receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. | Let‘s walk through each of these macros in detail. @@ -195,21 +198,12 @@ Property declarations within [`@Instantiable`](#instantiable) types decorated wi `@Forwarded` enables injecting runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` properties are unlikely to conform to types decorated with the `@Instantiable` macro. -A single `@Instantiable` type may have at most one `@Forwarded`-decorated property. `@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. +`@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. A `ForwardingInstantiator` has an `instantiate(_ argument: ArgumentToForward) -> InstantiableType` method that injects the forwarded property into the SafeDI tree and instantiates the type. This single-argument API requires that an `@Instantiable` type may have at most one `@Forwarded`-decorated property. If you need to forward multiple properties, create a new container type that stores both properties and forward the container type. ### @Received Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Received`](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. -### Macro cheat sheet - -| Macro | Decorating | Usage | -| ------ | ----------- | ----- | -| `@Instantiable` | Type declaration | Makes a type capable of being instantiated by SafeDI. | -| `@Instantiated` | Property declaration | Instantiates an instance or value when the enclosing type is instantiated. | -| `@Forwarded` | Property declaration | Propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree. | -| `@Received` | Property declaration | Receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. | - ### Delayed instantiation When you want to instantiate a dependency after `init(…)`, you need to declare an `Instantiator`-typed property as `@Instantiated` or `@Received`. @@ -250,7 +244,7 @@ public struct MyApp: App { public var body: some Scene { WindowGroup { if let user = userService.user { - // Returns a new instance of a `LoggedInContentView`. + // Returns a new instance of a `LoggedInContentView` that has a forwarded, non-optional User property. loggedInContentViewInstantiator.instantiate(user) } else { // Returns a new instance of a `LoggedOutContentView`. @@ -273,7 +267,7 @@ public struct MyApp: App { @Instantiated private let loggedOutContentViewInstantiator: Instantiator - /// An instance of an observable `UserService` that is instantiated when `MyApp` is instantiated. + /// An instance of an observable `UserService` that is instantiated when `MyApp` is instantiated. This object updates whenever the user changes. @ObservedObject @Instantiated private var userService: UserService @@ -303,7 +297,7 @@ To instantiate a dependency after a property‘s enclosing type is initialized, #### SwiftUI -It is important to avoid decorating initializer-injected dependencies with the [`@State`](https://developer.apple.com/documentation/swiftui/state) and [`@StateObject`](https://developer.apple.com/documentation/swiftui/stateobject) property wrappers. Apple‘s documentation for both of these property wrappers makes it clear. +Per Apple‘s documentation, it is important to avoid decorating initializer-injected dependencies with the [`@State`](https://developer.apple.com/documentation/swiftui/state) or [`@StateObject`](https://developer.apple.com/documentation/swiftui/stateobject) property wrappers. The `@State` documentation reads: @@ -313,7 +307,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) instead of `@StateObject`. Note that `@Instantiated` objects will be re-initialized when a view‘s parent view is invalidated. +`@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/). ### Migrating to SafeDI From 43637ff42f6fab5169ae387dee67408098de85bd Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 23 Dec 2023 16:54:09 -0800 Subject: [PATCH 09/18] Simplify intra-README hyperlink usage --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 262685e5..b85a247e 100644 --- a/README.md +++ b/README.md @@ -43,10 +43,10 @@ There are a total of four macros in the SafeDI library: | Macro | Decorating | Usage | | ------ | ----------- | ----- | -| `@Instantiable` | Type or extension declaration | Makes a type capable of being instantiated by SafeDI. | -| `@Instantiated` | Property declaration | Instantiates an instance or value when the enclosing `@Instantiable`-decorated type is instantiated. | -| `@Forwarded` | Property declaration | Propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree. | -| `@Received` | Property declaration | Receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. | +| [`@Instantiable`](#instantiable) | Type or extension declaration | Makes a type capable of being instantiated by SafeDI. | +| [`@Instantiated`](#instantiated) | Property declaration | Instantiates an instance or value when the enclosing `@Instantiable`-decorated type is instantiated. | +| [`@Forwarded`](#forwarded) | Property declaration | Propagates a runtime-created instance or value (e.g. a User object, network response, or customer input) down the dependency tree. | +| [`@Received`](#received) | Property declaration | Receives an instance or value from an `@Instantiated` or `@Forwarded` property further up the dependency tree. | Let‘s walk through each of these macros in detail. @@ -156,7 +156,7 @@ extension SecurePersistentStorage { ### @Instantiated -Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Instantiated`](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be [`@Received`](#received) by objects instantiated further down the dependency tree. +Property declarations within `@Instantiable` types decorated with [`@Instantiated`](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be `@Received` by objects instantiated further down the dependency tree. `@Instantiated`-decorated properties must be declared as an `@Instantiable` type, or of an `additionalType` listed in a `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. @@ -194,7 +194,7 @@ public struct ParentView: View { ### @Forwarded -Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` function. A `@Forwarded`-decorated property is available to be [`@Received`](#received) by objects instantiated further down the dependency tree. +Property declarations within `@Instantiable` types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` function. A `@Forwarded`-decorated property is available to be `@Received` by objects instantiated further down the dependency tree. `@Forwarded` enables injecting runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` properties are unlikely to conform to types decorated with the `@Instantiable` macro. @@ -202,7 +202,7 @@ Property declarations within [`@Instantiable`](#instantiable) types decorated wi ### @Received -Property declarations within [`@Instantiable`](#instantiable) types decorated with [`@Received`](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be [`@Instantiated`](#instantiated) or [`@Forwarded`](#forwarded) by an object higher up in the dependency tree. +Property declarations within `@Instantiable` types decorated with [`@Received`](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be `@Instantiated` or `@Forwarded` by an object higher up in the dependency tree. ### Delayed instantiation From 422d81664a6da480caae5d6e369e8af097dc6286 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 23 Dec 2023 16:58:27 -0800 Subject: [PATCH 10/18] Wordsmithing, typos, and more reducing repetition --- README.md | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index b85a247e..3ace3e6c 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ Every `@Instantiable`-decorated type must be: 2. Have a `public init(…)` or `open init(…)` that receives every injectable property -The `@Instantiable` guides engineers through satisfying these requirements with build-time FixIts. +The `@Instantiable` macro guides engineers through satisfying these requirements with build-time FixIts. Here is a sample `UserService` implementation that is `@Instantiable`: @@ -95,7 +95,7 @@ public final class UserService { // MARK: Private - /// An auth service instance that is instantiated when the `DefaultUserService` is instantiated. + /// An auth service instance that is instantiated when the `UserService` is instantiated. @Instantiated private let authService: AuthService @@ -158,13 +158,13 @@ extension SecurePersistentStorage { Property declarations within `@Instantiable` types decorated with [`@Instantiated`](Sources/SafeDI/PropertyDecoration/Instantiated.swift) are instantiated when its enclosing type is instantiated. `@Instantiated`-decorated properties are available to be `@Received` by objects instantiated further down the dependency tree. -`@Instantiated`-decorated properties must be declared as an `@Instantiable` type, or of an `additionalType` listed in a `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. +`@Instantiated`-decorated properties must be an `@Instantiable` type, or of an `additionalType` listed in an `@Instantiable(fulfillingAdditionalTypes:)`‘s declaration. #### Utilizing @Instantiated with type erased properties When you want to instantiate a type-erased property, you may specify which concrete type you expect to fulfill your property by utilizing `@Instantiated`‘s `fulfilledByType` parameter. -The `fulfilledByType` parameter takes a `String` of the name of the concrete type that will be assigned to the type-erased property. Representing the type as a string literal allows for dependency inversion: the code that receives the concrete type does not need to have a dependency on the module that defines the concrete type. +The `fulfilledByType` parameter takes a `String` identical to the type name of the concrete type that will be assigned to the type-erased property. Representing the type as a string literal allows for dependency inversion: the code that receives the concrete type does not need to have a dependency on the module that defines the concrete type. ```swift import SwiftUI @@ -183,9 +183,7 @@ public struct ParentView: View { } // The Instantiator‘s `instantiate()` function will build a view of type `ChildView`. - // Because the type is passed in as a string literal, this code does not need to - // have a dependency on the module that defines `ChildView`. All that is required - // for this code to compile is for there to be an + // All that is required for this code to compile is for there to be an // `@Instantiable public struct ChildView: View` in the codebase. @Instantiated(fulfilledByType: "ChildView") private let childViewBuilder: Instantiator @@ -196,9 +194,9 @@ public struct ParentView: View { Property declarations within `@Instantiable` types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` function. A `@Forwarded`-decorated property is available to be `@Received` by objects instantiated further down the dependency tree. -`@Forwarded` enables injecting runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` properties are unlikely to conform to types decorated with the `@Instantiable` macro. +`@Forwarded` enables the injection of runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` property types do not need to be decorated with the `@Instantiable` macro. -`@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. A `ForwardingInstantiator` has an `instantiate(_ argument: ArgumentToForward) -> InstantiableType` method that injects the forwarded property into the SafeDI tree and instantiates the type. This single-argument API requires that an `@Instantiable` type may have at most one `@Forwarded`-decorated property. If you need to forward multiple properties, create a new container type that stores both properties and forward the container type. +`@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. A `ForwardingInstantiator` has an `instantiate(_ argument: ArgumentToForward) -> InstantiableType` function that injects the forwarded property into the SafeDI tree and instantiates the type. This single-argument function necessitates that an `@Instantiable` type may have at most one `@Forwarded`-decorated property. If you need to forward multiple properties, create a new container type that stores both properties and forward the container type. ### @Received From 7cf3540445ddd7547656404045c1e0ca7e3c65df Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sat, 23 Dec 2023 19:30:30 -0800 Subject: [PATCH 11/18] Add missing macro declaration in example --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3ace3e6c..a6c8f127 100644 --- a/README.md +++ b/README.md @@ -69,6 +69,7 @@ Here is a sample `UserService` implementation that is `@Instantiable`: ```swift import SafeDI +@Instantiable public final class UserService { // MARK: Initialization From 45119b3ec0786d829924ce96d5816d2451a6e14e Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sun, 24 Dec 2023 08:43:40 -0800 Subject: [PATCH 12/18] Make @Forwarded easier to understand --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index a6c8f127..a3ccb5ce 100644 --- a/README.md +++ b/README.md @@ -193,11 +193,11 @@ public struct ParentView: View { ### @Forwarded -Property declarations within `@Instantiable` types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) are forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) instance’s `instantiate(…)` function. A `@Forwarded`-decorated property is available to be `@Received` by objects instantiated further down the dependency tree. +Property declarations within `@Instantiable` types decorated with [`@Forwarded`](Sources/SafeDI/PropertyDecoration/Forwarded.swift) represent dependencies that come from the runtime, e.g. user input or backend-delivered content. Like an `@Instantiated`-decorated property, a `@Forwarded`-decorated property is available to be `@Received` by objects instantiated further down the dependency tree. -`@Forwarded` enables the injection of runtime-created instances and values into the SafeDI dependency tree. `@Forwarded` properties are often representations of user input or backend-delivered content. `@Forwarded` property types do not need to be decorated with the `@Instantiable` macro. +A `@Forwarded` property is forwarded into the SafeDI dependency tree by a [`ForwardingInstantiator`](#forwardinginstantiator)’s `instantiate(_ argument: ArgumentToForward) -> InstantiableType` function that creates an instance of the property’s enclosing type. `@Instantiable` types with a `@Forwarded`-decorated property can _only_ be instantiated utilizing a `ForwardingInstantiator`. -`@Instantiable` types with a `@Forwarded`-decorated property can only be instantiated utilizing a `ForwardingInstantiator`. A `ForwardingInstantiator` has an `instantiate(_ argument: ArgumentToForward) -> InstantiableType` function that injects the forwarded property into the SafeDI tree and instantiates the type. This single-argument function necessitates that an `@Instantiable` type may have at most one `@Forwarded`-decorated property. If you need to forward multiple properties, create a new container type that stores both properties and forward the container type. +A `ForwardingInstantiator`‘s single-argument `instantiate` function necessitates that an `@Instantiable` type may have at most one `@Forwarded`-decorated property. If you need to forward multiple properties, create a new container type that stores both properties and forward the container type. Forwarded property types do not need to be decorated with the `@Instantiable` macro. ### @Received From 573324e471b69a001e505a137733b99fd36e0e9b Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Sun, 24 Dec 2023 08:43:50 -0800 Subject: [PATCH 13/18] Better linking --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a3ccb5ce..c7f0031c 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ When you want to instantiate a dependency after `init(…)`, you need to declare #### Instantiator -The `Instantiator` type is how SafeDI enables deferred instantiation of an `@Instantiable` type that has no `@Forwarded`-decorated properties. `Instantiator` has a single generic that matches the type of the to-be-instantiated instance. Creating an `Instantiator` property is as simple as creating any other property in the SafeDI ecosystem: +The [`Instantiator`](Sources/SafeDI/DelayedInstantiation/Instantiator.swift) type is how SafeDI enables deferred instantiation of an `@Instantiable` type that has no `@Forwarded`-decorated properties. `Instantiator` has a single generic that matches the type of the to-be-instantiated instance. Creating an `Instantiator` property is as simple as creating any other property in the SafeDI ecosystem: ```swift @Instantiable @@ -235,7 +235,7 @@ It is possible to write a `Instantiator` with a type-erased generic by utilizing #### ForwardingInstantiator -The `ForwardingInstantiator` type is how SafeDI enables instantiating any `@Instantiable` type with a `@Forwarded`-decorated property. `ForwardingInstantiator` has two generics. The first generic must match the type of the `@Forwarded`-decorated property. The second generic matches the type of the to-be-instantiated instance. +The [`ForwardingInstantiator`](Sources/SafeDI/DelayedInstantiation/ForwardingInstantiator.swift) type is how SafeDI enables instantiating any `@Instantiable` type with a `@Forwarded`-decorated property. `ForwardingInstantiator` has two generics. The first generic must match the type of the `@Forwarded`-decorated property. The second generic matches the type of the to-be-instantiated instance. ```swift @Instantiable From 6d63f1f2bdd6c920e865ce7fb68a468d2b75ed8a Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 25 Dec 2023 08:36:49 -0800 Subject: [PATCH 14/18] fix typo Co-authored-by: Nick Entin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c7f0031c..2ca5ed62 100644 --- a/README.md +++ b/README.md @@ -310,7 +310,7 @@ The `@StateObject` documentation reads: ### Migrating to SafeDI -It is strongly recommended that projects adopting SafeDI start their migration by identifying the root of their dependency tree and making it `@Instantiable`. Once your root object has adopted SafeDI, continue migrating dependencies to SafeDI in either a breadth-first or depth-first manner. As your adoption of SafeDI progression, you‘ll find that you are removing more code than you are adding: many of your dependencies are likely being passed through intermediary objects that do not utilize the dependency except to instantiate a dependency deeper in the tree. Once types further down the dependency tree have adopted SafeDI, you will be able to avoid receiving dependencies in intermediary types. +It is strongly recommended that projects adopting SafeDI start their migration by identifying the root of their dependency tree and making it `@Instantiable`. Once your root object has adopted SafeDI, continue migrating dependencies to SafeDI in either a breadth-first or depth-first manner. As your adoption of SafeDI progresses, you‘ll find that you are removing more code than you are adding: many of your dependencies are likely being passed through intermediary objects that do not utilize the dependency except to instantiate a dependency deeper in the tree. Once types further down the dependency tree have adopted SafeDI, you will be able to avoid receiving dependencies in intermediary types. ## Example App From 521a99a4c308e9bceef1065372647cb75fe85266 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 25 Dec 2023 10:26:53 -0800 Subject: [PATCH 15/18] Make it clear that you do need to call the root's init() function --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 2ca5ed62..c55eb6d3 100644 --- a/README.md +++ b/README.md @@ -290,7 +290,7 @@ SafeDI is designed to be simple to adopt and minimize architectural changes requ #### Instantiating objects -In a manual DI system, it is common to directly call your dependencies‘ `init(…)` functions. When utilizing SafeDI, you must rely on `@Instantiated`-decorated properties to instantiate your dependencies for you. Calling a dependency‘s `init(…)` function directly effectively exits the SafeDI-built dependency tree, which removes property lifecycle guarantees. +In a manual DI system, it is common to directly call your dependencies‘ `init(…)` functions. When utilizing SafeDI, you must rely on `@Instantiated`-decorated properties to instantiate your dependencies for you. Calling a dependency‘s `init(…)` function directly effectively exits the SafeDI-built dependency tree, which removes property lifecycle guarantees. Similarly, you must call the generated `init()` function on your dependency tree‘s root and not its memberwise `init(…)` function in order to create the SafeDI dependency tree. To instantiate a dependency after a property‘s enclosing type is initialized, you must utilize an instantiated or received `Instantiator` or `ForwardingInstantiator` instance. From e7581ee6b249f57e3037ff185182868543335ff3 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Mon, 25 Dec 2023 13:27:34 -0800 Subject: [PATCH 16/18] Add an example under @Received --- README.md | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/README.md b/README.md index c55eb6d3..c37e1515 100644 --- a/README.md +++ b/README.md @@ -203,6 +203,60 @@ A `ForwardingInstantiator`‘s single-argument `instantiate` function necessitat Property declarations within `@Instantiable` types decorated with [`@Received`](Sources/SafeDI/PropertyDecoration/Received.swift) are injected into the enclosing type‘s initializer. Received properties must be `@Instantiated` or `@Forwarded` by an object higher up in the dependency tree. +Here we have a `LoggedInContentView` whose forwarded `user` property is received by an `UpdateUserService` further down the dependency tree. + +``` +@Instantiable +public struct LoggedInContentView: View { + public init(user: User, userDetailsService: UserDetailsService) { + self.user = user + self.userService = userService + } + + public var body: some View { + ... // Instantiates and displays a ProfileView when a button is pressed. + } + + @Forwarded + private let user: User + + @Instantiated(fulfilledByType: "ProfileView") + private let profileViewBuilder: Instantiator +} + +@Instantiable +public struct ProfileView: View { + public init(updateUserService: UpdateUserService) { + self.updateUserService = updateUserService + } + + public var body: some View { + ... // Allows for updating user information. + } + + @Instantiated + private let updateUserService: UpdateUserService +} + +@Instantiable +public final class UpdateUserService { + public init(user: User) { + self.user = user + urlSession = .shared + } + + public func updateUserName(to newName: String) async { + // Updates the user name. + } + + // The user object which is received from the LoggedInContentView. + @Received + private let user: User + + private let urlSession: URLSession +} +``` + ### Delayed instantiation When you want to instantiate a dependency after `init(…)`, you need to declare an `Instantiator`-typed property as `@Instantiated` or `@Received`. From d41f445c48e4fd3243e89c5e9fd81b3332786055 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 26 Dec 2023 09:01:51 -0800 Subject: [PATCH 17/18] Update @Instantiable type requirements --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c37e1515..83d8e1fd 100644 --- a/README.md +++ b/README.md @@ -60,9 +60,9 @@ Every `@Instantiable`-decorated type must be: 1. `public` or `open` -2. Have a `public init(…)` or `open init(…)` that receives every injectable property +2. Have a `public init(…)` or `open init(…)` that has an argument for every injectable property and no other arguments -The `@Instantiable` macro guides engineers through satisfying these requirements with build-time FixIts. +The `@Instantiable` macro guides engineers through satisfying these requirements with code generation and build-time FixIts. Here is a sample `UserService` implementation that is `@Instantiable`: From e3c06b032877f1084bf1259b4a17acbcc11c3b55 Mon Sep 17 00:00:00 2001 From: Dan Federman Date: Tue, 26 Dec 2023 09:16:51 -0800 Subject: [PATCH 18/18] markdown checkmark style --- README.md | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 83d8e1fd..127b4264 100644 --- a/README.md +++ b/README.md @@ -9,29 +9,29 @@ Compile-time safe dependency injection for Swift projects. SafeDI is built for e ## Features -✅ Compile-time safe +- [x] Compile-time safe -✅ Thread safe +- [x] Thread safe -✅ Hierarchical dependency scoping +- [x] Hierarchical dependency scoping -✅ Constructor injection +- [x] Constructor injection -✅ Multi-module support +- [x] Multi-module support -✅ Dependency inversion support +- [x] Dependency inversion support -✅ Transitive dependency solving +- [x] Transitive dependency solving -✅ Cycle detection +- [x] Cycle detection -✅ Architecture independent +- [x] Architecture independent -✅ Simple integration: no DI-specific types or generics required +- [x] Simple integration: no DI-specific types or generics required -✅ Easy testing: every type has a memberwise initializer +- [x] Easy testing: every type has a memberwise initializer -✅ Clear error messages: never debug generated code +- [x] Clear error messages: never debug generated code ## Using SafeDI