注入 1.0.3

注入 1.0.3

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布最新发布2016 年 8 月
SPM支持 SPM

Andreas Ernst 维护。



注入 1.0.3

  • 作者:
  • coolsamson7

inject

Inject 是一个 Swift 依赖注入容器,它采用了 Spring 的基本想法(尽可能实现),并且利用 Swift 语言特性(如闭包)来提供一个简单直观的 API。

除了核心功能,还实现了一些其他概念

  • 基本的反射和类型自省功能
  • 配置框架
  • 日志框架
  • 跟踪框架
  • 并发类
  • XML 解析器
  • 类型转换设施

但让我们再次回到依赖容器吧::)

dependency injection 容器究竟是什么呢?

基本思想是有一个中心对象,它了解所有不同类型的对象和对象依赖,并且其任务是通过对字段(通过属性设置器、方法或适当的构造函数调用)进行填充来适当地实例化和组装它们。类不必了解当前基础设施的任何信息——例如协议的实现细节或特定的配置值——因为这些知识完全由容器负责并注入到类中。

如果您考虑单元测试,其中服务实现需要通过某种本地变体(例如模拟)进行交换,那么您就能体会到它的好处。

另一个主要好处是,对象的生存周期也由一个中心实例管理。一方面,这避免了在代码中到处使用单例模式——这简直是混乱——另一方面,它还允许其他功能,例如会话作用域的对象,或者调用关闭整个容器——释放资源——的功能。

功能

以下是支持的功能摘要

  • 通过流畅接口或 XML 规范指定 bean
  • 完整的依赖管理,包括循环检测
  • 对所有定义进行类型安全检查
  • 集成管理配置值
  • 类似于 Spring 的 @Inject 自动修复机制的注入
  • 支持不同的作用域,包括 singletonprotoype 作为内置口味
  • 支持懒加载 bean
  • 支持Bean模板
  • 生命周期方法(例如 postConstruct
  • BeanPostProcessor
  • FactoryBean
  • 支持分层容器,继承Bean(和后处理器)
  • 支持在XML中解析占位符(例如 ${property=<default>}
  • 支持在XML中使用自定义命名空间处理器
  • XML中的自动类型转换和数字强转

文档

有关详细信息,请访问

示例

让我们看看一些简单的示例。

let environment = try! Environment(name: "my first environment")

try! environment
   // a bar created by the default constructor

   .define(environment.bean(Bar.self, factory: Bar.init))

   // a foo that depends on bar

   .define(environment.bean(Foo.self, factory: {
            return Foo(bar: try! environment.getBean(Bar.self))
        }).requires(class: Bar.self))

   // get goin'

   .startup()

在环境配置后,可以通过getBean()函数简单地检索Bean。

let foo = try environment.getBean(Foo.self)

在幕后,所有Bean定义都将进行验证,例如查找循环依赖或无法解决的依赖性,并且所有单例Bean都将被立即构建。

其他注入—这里是指属性注入—可以通过流畅接口表达

environment.define(environment.bean(Foo.self, id: "foo-1")
   .property("name", value: "foo")
   .property("number", value: 7))

注入

Java @Inject注解的类似概念已经提供,允许您在类级别上定义注入。

public class AbstractConfigurationSource : NSObject, Bean, BeanDescriptorInitializer, ... {
    // MARK: instance data

    ...
    var configurationManager : ConfigurationManager? = nil // injected

    ...

    // MARK: implement BeanDescriptorInitializer

    public func initializeBeanDescriptor(beanDescriptor : BeanDescriptor) {
        beanDescriptor["configurationManager"].inject(InjectBean())
    }

    // MARK: implement Bean

    // we know, that all injections have been executed....
    public func postConstruct() throws -> Void {
        try configurationManager!.addSource(self)
    }

作用域

作用域确定Bean实例何时以及创建多少次。

  • 默认值为“singleton”,它将一次创建一个实例并缓存该值。
  • “prototype”将在每次请求Bean时重新创建一个新的实例。

示例:

environment.define(environment.bean(Foo.self)
   .scope("prototype")
   .property("name", value: "foo")
   .property("number", value: 7))

可以通过在当前环境中定义实现类(例如会话作用域)简单地添加其他作用域。

懒加载Bean

标记为懒加载的Bean将在第一次请求后构建。

工厂Bean

工厂Bean是实现了特定协议的Bean,它依次创建其他Bean。

environment
   .define(environment.bean(FooFactory.self)
      .property("someProperty", value: "...") // configure the factory....
      .target(Foo.self) // i will create foo's
    )

let foo = environment.getBean(Foo.self) // is created by the factory!

抽象Bean

可以定义一个Bean骨架(可能隐藏丑陋的技术参数),然后通过添加缺失的部分由程序员完成配置。

environment
   // a template

   .define(environment.bean(Foo.self, id: "foo-template", abstract: true)
      .property("url", value: "...")
      .property("port", value: 8080))

   // the concrete bean

   .define(environment.bean(Foo.self, parent: "foo-template")
      .property("missing", value: "foo") // add missing properties
   )

通常,模板是父环境的一部分,用于分离技术项。

Bean后处理器

Bean后处理器是实现了特定协议的类,用于在容器中调用以修改将被构建的实例。

生命周期回调

实现不同协议的类可以实现,当容器创建实例时将由容器调用。最重要的是在创建实例并在所有后处理器执行完毕后执行的postConstruct

配置值

每个容器定义了一个中央注册表,用于维护来自不同来源的配置值,可以通过统一的API检索。

let environment = ...
environment.addConfigurationSource(ProcessInfoConfigurationSource()) // process info
environment.addConfigurationSource(PlistConfigurationSource(name: "Info")) // will read Info.plist in the current bundle

// retrieve some values

environment.getConfigurationValue(Int.self, key: "SIMULATOR_MAINSCREEN_HEIGHT", defaultValue: 100) // implicit conversion!

XML配置

对于所有XML爱好者(:-)),提供了一个用于原始(至少是其子集)Spring Schema的XML解析器。

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns:configuration="http://www.springframework.org/schema/configuration"
        xmlns="http://www.springframework.org/schema/beans"
        xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
                        http://www.springframework.org/schema/configuration http://www.springframework.org/schema/util/spring-util.xsd">

    <!-- configuration values are collected from different sources and can be referenced in xml and the api -->
    <!-- in addition to static values dynamic sources are supported that will trigger listeners or simply change injected values on the fly-->

    <!-- here are some examples for sources -->

    <!-- builtin namespace & corresponding handler to set configuration values -->

    <configuration:configuration namespace="com.foo">
        <configuration:define key="bar" type="Int" value="1"/>
    </configuration:configuration>

    <!-- other supported sources are -->

    <!-- the current process info values, e.g. "PATH" -->

    <bean class="Inject.ProcessInfoConfigurationSource"/>

    <!-- a plist -->

    <bean class="Inject.PlistConfigurationSource">
        <property name="name" value="Info"/>
    </bean>

    <!-- a post processor will be called by the container after construction giving it the chance -->
    <!-- to modify it or completely exchange it with another object ( proxy... ) -->

    <!-- here we simple print every bean on stdout...:-) -->

    <bean class="SamplePostProcessor"/>

    <!-- create some foo's -->

    <!-- depends-on will switch the instantiation order -->

    <bean id="foo-1" class="Foo" depends-on="foo-2">
        <property name="id" value="foo-1"/>
        <!-- references an unknown configuration key, which will set the default value instead... -->
        <property name="number" value="${dunno=1}"/>
    </bean>

    <bean id="foo-2" class="Foo">
        <property name="id" value="foo-2"/>
        <!-- same thing recursively -->
        <property name="number" value="${dunno=${wtf=1}}"/>
    </bean>

    <!-- scope prototype means that whenever the bean is requestd a new instance will be created ( default scope is singleton ) -->
    <!-- other scopes yould be easily added, e.g. a session scope... -->

    <bean id="foo-prototype" class="Foo" scope="prototype">
        <property name="id" value="foo-prototype"/>
        <!-- this should work... the : separates the namespace from the key! -->
        <property name="number" value="${com.foo:bar}"/>
    </bean>

    <!-- bar will be injected by all foo's. Obviously the bar needs to be constructed first -->

    <bean id="bar-parent" class="Bar" abstract="true">
        <property name="magic" value="4711"/>
    </bean>

    <!-- will inherit the magic number -->
    <!-- lazy means that it will be constructed when requested for the first time -->

    <bean id="bar" class="Bar" parent="bar-parent" lazy="true">
        <property name="id" value="bar"/>
    </bean>

    <!-- both foo's will inject the bar instance -->

    <!-- baz factory will create Baz instances... -->

    <bean class="BazFactory" target="Baz">
        <property name="name" value="factory"/>
        <!-- will be set as the baz id... -->
        <property name="id" value="id"/>
    </bean>

    <!-- bazongs -->

    <bean id="bazong-1" class="Bazong">
        <property name="id" value="id"/>
        <!-- by reference -->
        <property name="foo" ref="foo-1"/>
    </bean>

    <bean id="bazong-2" class="Bazong">
        <property name="id" value="id"/>
        <!-- in-place -->
        <property name="foo">
            <bean class="Foo">
                <property name="id" value="foo-3"/>
                <property name="number" value="1"/>
            </bean>
        </property>
    </bean>
</beans>
var environment = Environment(name: "environment")
var data : NSData = ...
environment
   .loadXML(data)
   .startup()  

日志记录

除了注入容器之外,还实现并集成了日志框架。

一旦配置了单例

// a composition of the different possible log entry constituents

let formatter = LogFormatter.timestamp("dd/M/yyyy, H:mm:s") + " [" + LogFormatter.logger() + "] " + LogFormatter.level() + " - " + LogFormatter.message()
let consoleLogger = ConsoleLog(name: "console", formatter: formatter, synchronize: false)

LogManager() 
           .registerLogger("", level : .OFF, logs: [QueuedLog(name: "console", delegate: consoleLogger)]) // root logger
           .registerLogger("Inject", level : .WARN) // will inherit all log destinations
           .registerLogger("Inject.Environment", level : .ALL)

提供了常用的方法

// this is usually a static var in a class!
var logger = LogManager.getLogger(forClass: MyClass.self) // will lookup with the fully qualified name

logger.warn("ouch!") // this is a autoclosure!

logger.fatal(SomeError(), message: "ouch")

使用ErrorType参数调用errorfatal函数。这两个函数将发出包含原始消息、错误表示法和当前堆栈跟踪的消息。

提供的日志目标包含

  • 控制台
  • 文件
  • nslog
  • 队列日志目标

队列日志目标使用分发队列。默认情况下,将创建一个序列队列,其目的是简单地序列化条目。在这种情况下,'synchronize: false'可以防止控制台操作与互斥锁同步

要求

  • iOS 8.0+
  • Xcode 7.0+

安装

限制

根据具体的bean定义,可能需要相应的类继承自 NSObject。这一限制是由于Swift缺乏反射支持。一旦语言发展,我将改变这一点...

路线图

  • 支持更多包管理器
  • 等待回复:-)

许可证

本项目采用MIT许可证发布。有关详情请参阅LICENSE