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-generated
Composition Root Access
Access any registered dependency as a static var
from your type implementing AutoSetup
:
MyDependencies.myType // Auto-generated
While 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.