SwiftDependencyContainer is a lightweight Dependency Container leveraging Swift Macros and code generation to make setting up and managing dependencies easier than ever.
Singleton objects are retained throughout the container's' lifetime. They are instantiated on demand when first accessed, or, if marked as eager, created when the container is bootstrapped.
Factory instances of the registered type are created each time they are resolved.
Dependencies required to instantiate an object using constructor injection are automatically resolved, provided they are registered in the container.
Register a single instance for multiple types, allowing for more flexible and maintainable code.
Manage dependencies with hashable keys, enabling the registration of different implementations for the same protocol or tpye.
You can use the Swift Package Manager to install SwiftDependencyContainer by adding it as a dependency to your Package.swift file:
dependencies: [
.package(url: "git@github.com:davidscheutz/SwiftDependencyContainer.git", from: "0.5.0")
]Make sure to add SwiftDependencyContainer as a dependency to your Target.
Select your Project -> Your Target -> Build Phases -> Add CodeGeneratorPlugin (SwiftDependencyContainer)
The code generation process now runs automatically during the build phase, every time you compile your project.
For a magical setup experience thanks to code generation.
Examples demonstrate each use case.
Use the following annotiations to register your dependencies:
@Singleton
@Singleton(isEager: true) // Instantiated when container is bootstrapped rather than at first access
@Singleton(MyProtocol.self) // Register dependency for additional types
class MyClass {
init(otherDependency: OtherClass) {} // Auto-inject supported
}@Factory
class MyBuilder {
init(otherDependency: OtherClass) {} // Auto-inject supported
}This is the entry point of your depdencies.
Simply define an object that conforms to the AutoSetup protocol.
import SwiftDependencyContainer
struct MyDependencies: AutoSetup {
let container = DependencyContainer()
}Once your project is built, the necessary code for registering and resolving dependencies will be automatically generated and ready to use.
To bootstrap your DependencyContainer call the setup method on your type that implements the AutoSetup protocol.
MyDependencies.setup()Note: After calling setup, you will no longer be able to register additional dependencies.
At this stage, you're all set! No additional code is required to use your dependencies.
You can access your dependencies in two ways:
Direct Access
Use the resolve method of a registered type to retrieve an instance from the DependencyContainer.
MyType.resolve() // Auto-generatedComposition Root Access
Access any registered dependency as a static var from your type implementing AutoSetup:
MyDependencies.myType // Auto-generatedWhile AutoSetup is convenient, some scenarios may require additional flexibility. In such cases, you can manually register additional dependencies by overriding the optional override method of the AutoSetup protocol.
struct MyDependencies: AutoSetup {
let container = DependencyContainer()
func override(_ container: DependencyContainer) throws {
try container.register(Storage.self) { UserDefaults() } // e.g. Register third-party SDKs
}
static var storage: Storage { resolve() } // Mimic API of auto-generated types
}Note: Please feel free to open a ticket if you feel like the usage of your override should be part of this framework!
Note: If you have a repetitive use case and believe it should be integrated into this framework, feel free to open a ticket!
For more details, check out the Examples.
For those who prefer the traditional way:
let container = DependencyContainer()try container.register { Singleton1() }
// resolve other co-dependencies
try container.register { Singleton2(other: try $0.resolve()) }
// register instance for another type
try container.register(Abstraction.self) { Singleton1() }
// register instance for several other types
try container.register([Abstraction1.self, Abstraction2.self]) { Singleton1() }
// register instance for a key
try container.register("MyKey") { Singleton1() }All register methods include an isEager: Bool parameter. Eager dependencies are resolved upon container bootstrap.
Note: Keys are required to be Hashable.
try container.bootstrap()let singleton: Singleton1 = try container.resolve()
let singleton1: Singleton1 = try container.resolve("MyKey")
let singleton2: Abstraction2 = try container.resolve()Contributions to SwiftDependencyContainer are welcomed and encouraged!
It is easy to get involved. Open an issue to discuss a new feature, write clean code, show some love using unit tests and open a Pull Request.
A list of contributors will be available through GitHub.
PS: Check the open issues and pull requests for existing discussions.
SwiftDependencyContainer is available under the MIT license.
This project uses Sourcery for the code generation.