https://github.com/pjechris/annotationinject
Compile-time Swift dependency injection annotations
https://github.com/pjechris/annotationinject
annotations cocoapods compile-time dependency-injection injection injection-container safety sourcery swift swinject
Last synced: 6 months ago
JSON representation
Compile-time Swift dependency injection annotations
- Host: GitHub
- URL: https://github.com/pjechris/annotationinject
- Owner: pjechris
- License: mit
- Created: 2018-04-19T10:03:39.000Z (over 7 years ago)
- Default Branch: master
- Last Pushed: 2024-02-12T09:37:26.000Z (over 1 year ago)
- Last Synced: 2025-04-12T15:56:58.051Z (6 months ago)
- Topics: annotations, cocoapods, compile-time, dependency-injection, injection, injection-container, safety, sourcery, swift, swinject
- Language: Swift
- Homepage:
- Size: 6.47 MB
- Stars: 43
- Watchers: 2
- Forks: 3
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# AnnotationInject



[](https://twitter.com/pjechris)Generate your dependency injections. Aimed for safety.
| | AnnotationInject
|---------------------|--------
| :statue_of_liberty: | 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](https://github.com/krzysztofzablocki/Sourcery) and [Swinject](https://github.com/Swinject/Swinject).
| :book: | 100% open source under the MIT license- [What's the issue with injection?](#whats-the-issue-with-injection)
- [Usage](#usage)
- [Available annotations](#available-annotations)
- [Caveats](#caveats)> 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).
## What's the issue with injection?
### Without annotations
Using a dependency injection library (say, Swinject) you need to **remember** to register your dependencies:```swift
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.
### With annotations
Annotations will generate your dependencies and make sure everything resolves at **compile time**.
```swift
/// 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!
## Usage
### 1. Annotate your dependencies
```
/// sourcery: inject
class CoffeeMaker {
init(heater: Heater) { }
}/// sourcery: inject
class Heater {
init() { }
}
```### 2. Add a build phase to generate dependencies
See [Installation](#installation) for more details.If not all dependencies can be resolved, the build phase will fail, preventing your code from compiling succesfully.
### 3. Add generated files and use generated code
```
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
```## Installation
> Note: AnnotationInject depends/relies on Sourcery for annotations declaration, and Swinject as dependency injecter.- Swift Package Manager
```swift
dependencies: [
.package(url: "https://github.com/pjechris/AnnotationInject.git", from: "0.6.0")
]
```
Then add a `Build phases` to your project:```shell
swift run annotationinject-cli --sources --output (--args imports= -args imports=>)
```- Swift Package Manager (Xcode)
Add AnnotationInject as dependency in Xcode then add this `Build phase` to your project:
```shell
SPM_CHECKOUT_DIR=${BUILD_DIR%Build/*}SourcePackages/checkouts/AnnotationInject
cd $SPM_CHECKOUT_DIR
/usr/bin/xcrun --sdk macosx swift run annotationinject-cli ...
```- CocoaPods
Add `pod AnnotationInject` to your `Podfile` and a new `Build phases` to your project:
```shell
"$(PODS_ROOT)"/AnnotationInject/Scripts/annotationinject --sources --output (--args imports= -args imports=>)
```> Note: You can pass all `sourcery` command line options to `annotationinject` script.
- Manually
1. Install [Swinject](https://github.com/Swinject/Swinject) and [Sourcery](https://github.com/krzysztofzablocki/Sourcery).
2. Copy-paste Sources and Templates folders inside and add a new `Build phases` to your project:
```shell
sourcery --templates --sources --output (--args imports= -args imports=>)
```## Available annotations
### `inject`
Registers a class into the dependency container.```swift
/// sourcery: inject
class CoffeeMaker { }
```Generated code
```swift
container.register(CoffeeMaker.self) {
return CoffeeMaker()
}extension SafeDependencyResolver {
func registeredService() -> CoffeeMaker {
return resolve(CoffeeMaker.self)!
}
}
```
Options
- name
- Define a name for the service. Generated method will use that name.
- scope
- See Swinject Object Scopes
- type
- Defines the type on which the class is registered. Use it when you want to resolve against a protocol.
```swift
/// 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.
```swift
// sourcery: inject
class CoffeeMaker {
init(heater: Heater) { }
// sourcery: inject
convenience init() {
self.init(heater: CoffeHeater())
}
}
```
Generated code
```swift
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.
```swift
// sourcery: inject
class CoffeeMaker {
/// sourcery: inject
var heater: Heater!
init() { }
}
```
Generated code
```swift
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.
```swift
class CoffeeMaker {
init(heater: Heater) { }
}
// sourcery: provider
class AppProvider {
static func instantiate(resolver: SafeDependencyResolver) -> CoffeeMaker {
return CoffeeMaker(heater: CoffeHeater())
}
}
```
Generated code
```swift
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.
## Caveats
_**Generated code does not compile because of missing imports**_
Set `--args imports= -args imports=>` 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.
## License
This project is released under the MIT License. Please see the LICENSE file for details.