Swinject-CodeGen 2.0.0

Swinject-CodeGen 2.0.0

测试已测试
语言语言 Obj-CObjective C
许可 MIT
发布上次发布2017年2月
SwiftSwift 版本3.0

维护者 Wolfgang Lutz



  • Swinject 贡献者

Swinject 代码生成

Swinject-CodeGen 通过生成明确的使用 Swinject 进行注册和解 resolved 的函数,提供了一种避免类值和名称字符串重复使用的方法。通过这种方式,我们还可以生成在 resolved 时使用的类型化的元组,从而使代码具有更好的文档性和更少的错误。

集成

  1. 在 .csv 或 .yml 文件中定义您的依赖关系(以下示例和示例文件中说明)
  2. 将生成代码调用添加到构建脚本阶段

对于 Cocoapods

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

对于 Carthage

$SRCROOT/Carthage/Checkouts/Swinject-CodeGen/bin/swinject_codegen -i baseInput.csv -o extensions/baseContainerExtension.swift
  1. 将生成的文件(此处:`extensions/baseContainerExtension.swift`)添加到 Xcode 中
  2. 如果您需要支持多个目标/有多个输入文件,则需要重复此步骤。

然后在每次构建运行时生成代码。

问题

在使 用 Swinject 时,每当进行以下操作时,就会出现很多重复的定义:

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

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

元组 (PersonType.self, name:"initializer") 在代码中变得非常冗余。

此外,在调用时使用参数,如以下示例所示:

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

`argument: "Spirit"` 部分在调用时不具有严格的类型。

我们通过使用代码生成来解决这两个问题。

输入格式

输入可以是 .csv 或者 .yml 格式

可以使用以下调用将 example.csv 转换为 example.csv.yml (也适用于 .yml):

./swinject_codegen -i example.csv -c

...

CSV

基本结构

我们定义的基本 CSV 结构如下:

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

上述示例将转换为:

PersonType; InjectablePerson; initializer

以生成 `registerPersonType_initializer` 和 `resolvePersonType_initializer` 函数。

请参考下面的示例进行更多示例。

我们决定使用 `;` 作为分隔符而不是 `,`,以便允许使用元组作为类型。

附加命令

Ruby 解析器允许使用 `//` 和 `#` 作为注释。空行将被忽略,可用于分组。

可以使用 `#= <header>` 来指定附加行,例如:`#= import KeychainAccess`

字典和数组作为参数

当使用类型化的字典或数组作为参数时,请使用 Array<Type> 替代 [Type],并使用 Dictionary<TypeA, TypeB> 替代 [TypeA:TypeB]

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

YAML

以下为 .yml 定义示例

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

生成示例

示例 A:源和目标具有相同的类

输入

PersonType

输出

// 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)
    }
}

示例 B:源和目标不同

输入

PersonType; InjectablePerson

输出

// 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)
    }
}

示例 C:不同源和目标类具有名称

输入

PersonType; InjectablePerson; initializer

输出

// 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)
    }
}

示例 D:具有单个显式命名的参数的不同源和目标

输入

PersonType; InjectablePerson; ; argumentName:ArgumentType

输出

// 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)
    }
}

示例 E:具有多个参数的不同源和目标,既有显式命名的也有非显式命名的

如果没有给出显式名称,则使用小写类型作为参数名称。

输入

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

输出

// 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)
    }
}

示例 F:具有名称的不同源和目标,多个参数,既有显式命名的也有非显式命名的

输入

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

输出

// 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)
    }
}

使用示例

使用开头提供的示例,我们可以现在代替

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

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

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

let initializerInjection = container.resolvePersonType_initializer()

同样

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

变为

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

迁移

脚本还会生成 migration.sh 文件(使用 -m 开关时),这些文件使用 sed 遍历代码并替换 resolve 和 register 的简单情况(即没有参数)。还没有自动迁移支持具有参数的情况。只需从项目根目录调用 .sh 文件并在 git-GUI 中比较结果即可。

结果

我们目前在tvOS和iOS上的两个中型应用中使用了代码生成。

我们发现,由于减少了重复内容和自动完成,我们的代码阅读和编写变得更加方便。我们还能够更好地了解通过依赖注入可用的类。更改一些定义会立即导致出现在错误信息中。我们能够使用当前实现替换所有出现的.resolve(.register(

贡献者

将代码生成与Swinject结合的原始想法来自Daniel DenglerDavid KrausWolfgang Lutz