Swift dependency injection annotations. Using Sourcery and Swinject.
Generate your dependency injections. Aimed for safety.
AnnotationInject | |
---|---|
🗽 | Free you from manually registering your dependencies. |
⚡ | Spend less time to configure and more time to code! |
🛡 | No more runtime crash because dependency is not up-to-date. Everything is checked at compile-time. |
👐 | Based on open source tools you like as Sourcery and Swinject. |
📖 | 100% open source under the MIT license |
Documentation for a specific release might slightly differ. If you have troubles please check the release doc first (by selecting the release in Github switch branches/tags).
Using a dependency injection library (say, Swinject) you need to remember to register your dependencies:
container.register(CoffeeMaker.self) { r in
return CoffeeMaker(heater: r.resolve()!) // Trouble ahead, not sure Heater is in fact registered!
}
/// later in your code
let coffeeMaker = container.resolve(CoffeeMaker.self) // crash, missing Heater dependency!
Running this code we’ll get a crash at runtime: we didn’t register any heater
, resulting in CoffeeMaker resolver to crash.
Annotations will generate your dependencies and make sure everything resolves at compile time.
/// sourcery: inject
class CoffeeMaker {
init(heater: Heater) {
}
}
This time we’ll get a compile time error because we forgot to declare a Heater
dependency. Houray!
/// sourcery: inject
class CoffeeMaker {
init(heater: Heater) { }
}
/// sourcery: inject
class Heater {
init() { }
}
See Installation for more details.
If not all dependencies can be resolved, the build phase will fail, preventing your code from compiling succesfully.
let resolver = Assembler([AnnotationAssembly()]).resolver
// `registeredService` is generated code. It is completely safe at compile time.
let coffeeMaker = resolver.registeredService() as CoffeeMaker
let heater = resolver.registeredService() as Heater
Note: AnnotationInject depends/relies on Sourcery for annotations declaration, and Swinject as dependency injecter.
dependencies: [
.package(url: "https://github.com/pjechris/AnnotationInject.git", from: "0.6.0")
]
Then add a Build phases
to your project:
swift run annotationinject-cli --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)
Add AnnotationInject as dependency in Xcode then add this Build phase
to your project:
SPM_CHECKOUT_DIR=${BUILD_DIR%Build/*}SourcePackages/checkouts/AnnotationInject
cd $SPM_CHECKOUT_DIR
/usr/bin/xcrun --sdk macosx swift run annotationinject-cli ...
Add pod AnnotationInject
to your Podfile
and a new Build phases
to your project:
"$(PODS_ROOT)"/AnnotationInject/Scripts/annotationinject --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)
Note: You can pass all
sourcery
command line options toannotationinject
script.
Copy-paste Sources and Templates folders inside and add a new Build phases
to your project:
sourcery --templates <path to copied templates> --sources <path to your sources> --output <path to output generated code> (--args imports=<MyLib1> -args imports=<MyLib2>>)
inject
Registers a class into the dependency container.
/// sourcery: inject
class CoffeeMaker { }
container.register(CoffeeMaker.self) {
return CoffeeMaker()
}
extension SafeDependencyResolver {
func registeredService() -> CoffeeMaker {
return resolve(CoffeeMaker.self)!
}
}
/// sourcery:inject: scope = "weak", type = "Maker", name = "Arabica"
class CoffeeMaker: Maker { }
inject
(init)Registers a specific init for injection. If annotation is not provided, first found is used.
Note: Class still needs to be
inject
annotated.
// sourcery: inject
class CoffeeMaker {
init(heater: Heater) { }
// sourcery: inject
convenience init() {
self.init(heater: CoffeHeater())
}
}
container.register(CoffeeMaker.self) {
return CoffeeMaker()
}
extension SafeDependencyResolver {
func registeredService() -> CoffeeMaker {
return resolve(CoffeeMaker.self)!
}
}
inject
(attribute)Injects an attribute after init. Attribute requires to be marked as Optional (?
or !
).
Note: Class still needs to be
inject
annotated.
// sourcery: inject
class CoffeeMaker {
/// sourcery: inject
var heater: Heater!
init() { }
}
container.register(CoffeeMaker.self) {
return CoffeeMaker()
}
.initCompleted { service, resolver in
service.heater = resolver.registeredService()
}
provider
Uses a custom function to register your dependency. It is the same as implementing container.register
manually while keeping safety.
Note that provided method must be called instantiate
.
Note: If you’re providing 3rd party libraries (coming from Cocoapods for example), you will need to pass those imports to AnnotationInject using
args.imports MyLib,MyLib2,...
command line argument.
class CoffeeMaker {
init(heater: Heater) { }
}
// sourcery: provider
class AppProvider {
static func instantiate(resolver: SafeDependencyResolver) -> CoffeeMaker {
return CoffeeMaker(heater: CoffeHeater())
}
}
container.register(CoffeeMaker, factory: AppProvider.instantiate(resolver:))
extension SafeDependencyResolver {
func registeredService() -> CoffeeMaker {
return resolve(CoffeeMaker.self)!
}
}
provided
(no longer needed with 0.5.0)Declares a parameter as argument to define into the resolver method. Work on init and provider methods.
Generated code does not compile because of missing imports
Set --args imports=<MyLib1> -args imports=<MyLib2>>
so that generated code includes 3rd party libraries.
Foundation types (URLSession, NSNotificationCenter, …) are empty (.self) in generated code
Sourcery is not yet able to find those types. As such they are seen as non existent. Workaround: Define the surrounded type inside a Provider and give it foundation types.
Build phase is failing with no error reported
This might be coming from Sourcery having some incompatibilities with Xcode 11.4. Workaround: Install Sourcery using Homebrew then add to the build step SOURCERY_BINPATH=sourcery
as environment variable.
Pods/Sourcery/bin/Sourcery.app/Contents/MacOS/Sourcery: No such file or directory
You’re probably using Sourcery as a Cocoapods dependency which unfortunately doesn’t always work well. Workaround: Install Sourcery using Homebrew then add to the build step SOURCERY_BINPATH=sourcery
as environment variable.
This project is released under the MIT License. Please see the LICENSE file for details.