Code Monkey home page Code Monkey logo

swinject-codegen's Introduction

Swinject Code Generation

Build Status

Swinject-CodeGen provides a method to get rid of duplicate use of class values and namestrings, by generating explicit functions for registering and resolving using Swinject. Doing this, we also can generate typed tuples to use when resolving, thus allowing better documented and less error-prone code.

Installation

Cocoapods

Add

pod 'Swinject-CodeGen'

to your podfile.

Carthage

Add

github "Swinject/Swinject-CodeGeneration"

to your Cartfile.

Integration

  1. Define your dependencies in a .csv or .yml file (see below and example file)
  2. Add a call to generate the code as build script phase:

For Cocoapods:

$PODS_ROOT/Swinject-CodeGen/bin/swinject_codegen -i baseInput.csv -o extensions/baseContainerExtension.swift

For Carthage:

$SRCROOT/Carthage/Checkouts/Swinject-CodeGen/bin/swinject_codegen -i baseInput.csv -o extensions/baseContainerExtension.swift
  1. Add the generated file (here: extensions/baseContainerExtension.swift) to xcode
  2. Repeat if you need to support multiple targets/have multiple input files.

The code is then generated at every build run.

The Issue

When using Swinject, lots of duplicate definitions appear, whenever we do a

container.register(PersonType.self, name: "initializer") { r in
    InjectablePerson(pet: r.resolve(AnimalType.self)!)
}

let initializerInjection = container.resolve(PersonType.self, name:"initializer")!

the tuple (PersonType.self, name:"initializer") becomes very redundant across the code.

Furthermore, when using arguments, as done in

container.register(AnimalType.self) { _, name in Horse(name: name) }
let horse1 = container.resolve(AnimalType.self, argument: "Spirit") as! Horse

the argument: "Spirit" part is not strictly typed when calling it.

We propose a solution to both these problems by using CodeGeneration

Input Format

Input can be given as .csv or .yml

The call

./swinject_codegen -i example.csv -c

can be used to convert example.csv into example.csv.yml (also works for .yml).

CSV

Basic Structure

Our basic csv structure is defined as follows:

SourceClassName; TargetClassName; Identifier; Argument 1 ... 9

The example above would translate to

PersonType; InjectablePerson; initializer

to generate both a registerPersonType_initializer and a resolvePersonType_initializer function.

See the examples below for more examples.

We decided to use ; as delimiter instead of , to allow the use of tuples as types.

Additional Commands

The ruby parser allows using // and # for comments. Empty lines are ignored and can be used for grouping.

#= <header> can be used to specify additional lines, e.g. #= import KeychainAccess

Dictionaries and Arrays as Parameters

When using typed dictionaries or arrays as parameters, use Array<Type> instead of [Type] and Dictionary<TypeA, TypeB> instead of [TypeA:TypeB]:

PersonType; InjectablePerson; initializer; additionalNames:Array<String>; family:Dictionary<String, String>;

YAML

Example for a .yml definition:

---
HEADERS:
  - import ADependency
DEFINITIONS:
- service: PersonType
  component: InjectablePerson
  name: initializer
- service: PersonType
  component: InjectablePerson
- service: PersonType
  component: PersonType
- service: AnotherPersonType
  component: AnotherPersonType
- service: PersonType
  component: InjectablePerson
  arguments:
  - argument_name: argument_name
    argument_type: argument_type
- service: PersonType
  component: InjectablePerson
  arguments:
  - argument_name: argument_name
    argument_type: argument_type
  - argument_name: argument_typewithoutspecificname
    argument_type: argument_typeWithoutSpecificName
  - argument_name: title
    argument_type: String
  - argument_name: string
    argument_type: String
- service: PersonType
  component: InjectablePerson
  name: initializer
  arguments:
  - argument_name: argument_name
    argument_type: argument_type
  - argument_name: argument_typewithoutspecificname
    argument_type: argument_typeWithoutSpecificName
  - argument_name: title
    argument_type: String
  - argument_name: string
    argument_type: String

Generation Examples

Example A: Same class as source and target

Input

PersonType

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolvePersonType() -> PersonType {
        return self.resolve(PersonType.self)!
    }
}

extension Container {

    @discardableResult func registerPersonType(registerClosure: @escaping (_ resolver: Resolver) -> (PersonType)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, factory: registerClosure)
    }
}

Example B: Different source and target

Input

PersonType; InjectablePerson

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolveInjectablePerson() -> InjectablePerson {
        return self.resolve(PersonType.self) as! InjectablePerson
    }
}

extension Container {

    @discardableResult func registerInjectablePerson(registerClosure: @escaping (_ resolver: Resolver) -> (InjectablePerson)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, factory: registerClosure)
    }
}

Example C: Different source and target class with name

Input

PersonType; InjectablePerson; initializer

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolveInjectablePerson_initializer() -> InjectablePerson {
        return self.resolve(PersonType.self, name: "initializer") as! InjectablePerson
    }
}

extension Container {

    @discardableResult func registerInjectablePerson_initializer(registerClosure: @escaping (_ resolver: Resolver) -> (InjectablePerson)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, name: "initializer", factory: registerClosure)
    }
}

Example D: Different source and target with a single, explicitly named argument

Input

PersonType; InjectablePerson; ; argumentName:ArgumentType

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolveInjectablePerson(argumentName: ArgumentType) -> InjectablePerson {
        return self.resolve(PersonType.self, argument: argumentName) as! InjectablePerson
    }
}

extension Container {

    @discardableResult func registerInjectablePerson(registerClosure: @escaping (_ resolver: Resolver, _ argumentName: ArgumentType) -> (InjectablePerson)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, factory: registerClosure)
    }
}

Example E: Different source and target with multiple arguments, both explicitly named and not

If no explicit name is given, the lowercase type is used as argumentname.

Input

PersonType; InjectablePerson; ; argumentName:ArgumentType; ArgumentTypeWithoutSpecificName; title:String; String

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolveInjectablePerson(argumentName: ArgumentType, argumenttypewithoutspecificname: ArgumentTypeWithoutSpecificName, title: String, string: String) -> InjectablePerson {
        return self.resolve(PersonType.self, arguments: argumentName, argumenttypewithoutspecificname, title, string) as! InjectablePerson
    }
}

extension Container {

    @discardableResult func registerInjectablePerson(registerClosure: @escaping (_ resolver: Resolver, _ argumentName: ArgumentType, _ argumenttypewithoutspecificname: ArgumentTypeWithoutSpecificName, _ title: String, _ string: String) -> (InjectablePerson)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, factory: registerClosure)
    }
}

Example F: Different source and target with name with multiple arguments, both explicitly named and not

Input

PersonType; InjectablePerson; initializer; argumentName:ArgumentType; ArgumentTypeWithoutSpecificName; title:String; String

Output

// this code is autogenerated, do not modify!

import Swinject

extension Resolver {

    func resolveInjectablePerson_initializer(argumentName: ArgumentType, argumenttypewithoutspecificname: ArgumentTypeWithoutSpecificName, title: String, string: String) -> InjectablePerson {
        return self.resolve(PersonType.self, name: "initializer", arguments: argumentName, argumenttypewithoutspecificname, title, string) as! InjectablePerson
    }
}

extension Container {

    @discardableResult func registerInjectablePerson_initializer(registerClosure: @escaping (_ resolver: Resolver, _ argumentName: ArgumentType, _ argumenttypewithoutspecificname: ArgumentTypeWithoutSpecificName, _ title: String, _ string: String) -> (InjectablePerson)) -> ServiceEntry<PersonType> {
        return self.register(PersonType.self, name: "initializer", factory: registerClosure)
    }
}

Usage Examples

Using the examples given at the beginning, we can now instead of

container.register(PersonType.self, name: "initializer") { r in
    InjectablePerson(pet: r.resolve(AnimalType.self)!)
}

let initializerInjection = container.resolve(PersonType.self, name:"initializer")!

write:

container.registerPersonType_initializer { r in
    InjectablePerson(pet: r.resolve(AnimalType.self)!)
}

let initializerInjection = container.resolvePersonType_initializer()

Also

container.register(AnimalType.self) { _, name in Horse(name: name) }
let horse1 = container.resolve(AnimalType.self, argument: "Spirit")

becomes

container.registerAnimalType { (_, name:String) in
  Horse(name: name)
}
let horse1 = container.resolveAnimalType("Spirit")

Migration

The script also generates migration.sh files (when using the -m switch), which use sed to go through the code and replace simple cases (i.e. no arguments) of resolve and register. No automatic migration is available for cases with arguments, yet. Simply call the .sh file from the root of the project and compare the results in a git-GUI.

Results

We currently use the code generation in two medium-sized apps across tvOS and iOS.

We found our code to become much more convenient to read and write, due to reduced duplication and autocompletion. We also have a much better overview the classes available through dependency injection. Changing some definition immediately leads to information, where an error will occur. We were able to replace all our occurrences of .resolve( and .register( using the current implementation.

Contributors

The original idea for combining CodeGeneration and Swinject came from Daniel Dengler, David Kraus and Wolfgang Lutz.

swinject-codegen's People

Contributors

ddengler avatar lutzifer avatar yoichitgy avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

swinject-codegen's Issues

Use Pull Requests to Test Automatically before Merging into master

We've added tests to this project. Thanks @Lutzifer!

Now it's a very good practice to use Pull Requests to merge commits into master because the tests automatically run on Travis. You can keep master safe if you merge the Pull Requests after confirming the test results shown in the PRs.

It's ok to push a commit directly to master if it is a minor document update, but basically let's use PRs!

@Lutzifer plz close this issue if you got itπŸ˜€

Release v0.5.0

Let's release CodeGen after we resolve all issues/tickets.

Is there any way how to use Swinject's Object Scopes with CodeGen?

Hello,

is there currently any way how to use Swinject's Object Scopes with CodeGen? Most importantly with the Container (aka Singleton) scope?

I have AuthenticationModel class which I put into dependencies.csv and Swinject CodeGen correctly generated registerAuthenticationModel() for me.

So I registered it and appended .inObjectScope(.Container) call as per documentation at https://github.com/Swinject/Swinject/blob/master/Documentation/ObjectScopes.md

container.registerAuthenticationModel { (resolver) -> (AuthenticationModel) in
            AuthenticationModel()
}.inObjectScope(.Container)

But when I later resolve the model by:

container.resolveAuthenticationModel()

I get always new instance of AuthenticationModel - what am I doing wrong here?

Or does the .inObjectScope(.Container) need to be directly after register() call? Like inside the generated registerAuthenticationModel()?

So this:

func registerAuthenticationModel(registerClosure: (resolver: ResolverType) -> (AuthenticationModel)) -> ServiceEntry<AuthenticationModel> {
        return self.register(AuthenticationModel.self, factory: registerClosure)
    }

would be:

func registerAuthenticationModel(registerClosure: (resolver: ResolverType) -> (AuthenticationModel)) -> ServiceEntry<AuthenticationModel> {
        return self.register(AuthenticationModel.self, factory: registerClosure).inObjectScope(.Container)
    }

It there any way to persuade CodeGen to put it there? Or do I miss something here?

Thanks for advice!

Have a nice day
Tom

ADD_DEPENDENCY syntax

@Lutzifer I would suggest to replace this with a simple comment block allowing any text to be placed as code in the resulting header instead of explicitly defining dependencies. This would be a lot more flexible. What do you think?

How to test this?

we should add test cases to this, what would be a good way of specifying them in this case?

GitFlow

Can we use a gitflow workflow with master/dev/feature?

Rename executable file and project?

"CodeGen" is a well-known short name for "code generation". It looks good to rename our executable file and project to:

  • Executable: swinject_codegen
  • Project: Swinject-CodeGen

@Lutzifer: what do you think about the rename?

How to distribute the scripts?

We can use CocoaPods as Apple platform dependency management, Homebrew as Mac's package manager, or RubyGems because the scripts are written in Ruby.

Which one is good for our case?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.