An open API service indexing awesome lists of open source software.

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

Awesome Lists containing this project

README

          

# AnnotationInject

![Cocoapods](https://img.shields.io/static/v1?label=cocoapods&message=%E2%9C%93&color=24C28A&labelColor=444444)
![SPM](https://img.shields.io/static/v1?label=SPM&message=%E2%9C%93&color=24C28A&labelColor=444444)
![tests](https://github.com/pjechris/AnnotationInject/actions/workflows/testing.yml/badge.svg)
[![twitter](https://img.shields.io/badge/twitter-pjechris-1DA1F2?logo=twitter&logoColor=white)](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.