Swiftpack.co - Package - akane/AnnotationInject

AnnotationInject

Build Status Cocoapods

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 and Swinject. | :book: | 100% open source under the MIT license

Starting with 0.4.0, templates are written using Swift. If you have any trouble, please file a issue.

What's the issue with injection?

Without annotations

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.

With annotations

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!

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 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.

  • CocoaPods

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 to annotationinject script.

  • Manually
  1. Install Swinject and Sourcery.

  2. 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>>)
  • Swift Package Manager

We do not officially support Swift Package Manager as it does not have resources handling yet.

Available annotations

inject

Registers a class into the dependency container.

/// sourcery: inject
class CoffeeMaker { }
Generated code

container.register(CoffeeMaker.self) {
  return CoffeeMaker()
}

extension SafeDependencyResolver {
  func registeredService() -> CoffeeMaker {
    return resolve(CoffeeMaker.self)!
  }
}

Options

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.

/// sourcery:inject: scope = "weak", type = "Maker"
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())
  }
}
Generated code

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() { }
}
Generated code

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())
  }
}
Generated code

container.register(CoffeeMaker, factory: AppProvider.instantiate(resolver:))

extension SafeDependencyResolver {
  func registeredService() -> CoffeeMaker {
    return resolve(CoffeeMaker.self)!
  }
}

provided

Declares a parameter as argument to define into the esolver method. Work on init and provider methods.

// sourcery: inject
class Cat {
  init(/* sourcery: provided */ name: String, /* sourcery: provided */ age: Int)
}

// sourcery: provider
class AppProvider {
    static func instantiate(resolver: Resolver, /* sourcery: provided */ name: String, /* sourcery: provided */ age: Int) -> Cat {
        return Cat(name: String, age: Int)
    }
}

Caveats

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.

License

This project is released under the MIT License. Please see the LICENSE file for details.

Github

link
Stars: 21
Help us keep the lights on

Dependencies

Used By

Total: 0