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

https://github.com/coolsamson7/inject

Dependency Injection Container for Swift
https://github.com/coolsamson7/inject

injection injection-container logger swift xml-parser

Last synced: 9 months ago
JSON representation

Dependency Injection Container for Swift

Awesome Lists containing this project

README

          

# inject

[![Swift Version](https://img.shields.io/badge/Swift-3.1-F16D39.svg?style=flat)](https://developer.apple.com/swift)
[![Build Status](https://travis-ci.org/coolsamson7/inject.svg?style=flat)](https://travis-ci.org/coolsamson7/inject)
[![CocoaPods](https://img.shields.io/cocoapods/v/inject.svg)](https://github.com/coolsamson7/inject)
[![Platform](https://img.shields.io/cocoapods/p/inject.svg?style=flat)](http://cocoapods.org/pods/inject)
[![License][mit-badge]][mit-url]



`Inject` is a dependency injection container for Swift that picks up the basic `Spring` ideas - as far as they are possible to be implemented - and additionally utilizes the Swift language features - e.g. closures - in order to provide a simple and intuitive api.

In addition to the core a number of other concepts are implemented
* basic reflection and type introspection framework
* configuration framework
* logging & tracing framework
* concurrency classes
* xml parser
* type conversion facilities

But let's come back to the dependency container again :-)

# What's a dependency injection container anyway?

The basic idea is to have one central object that knows about all kind of different object types as well as object dependencies and whose task is to instantiate and assemble them appropriately by populating fields ( with property setters, methods or appropriate constructor calls ). Classes do not have to know anything about the current constallation - e.g. specific protocol implementation, or specific configuration values - as this know how is solely in the responsiblity of the container and injected into the classes.

If you think about unit testing, where service implementations need to be exchanged by some kind of local variants ( e.g. mocks ) you get a feeling for the benefits.

The other big benefit is that the lifecycle of objects is also managed by a central instance. This on the one hand avoids singleton patterns all over your code - which simply is a mess - and on the other hand allows for other features such as session scoped objects, or the possibility to shutdown the complete container - releasing ressources - with on call.

# Features

Here is a summary of the supported features
* specification of beans via a fluent interface or xml
* full dependency management including cycle detection
* all defintions are checked for typesafeness
* integrated management of configuration values
* injections resembling the spring `@Inject` autowiring mechanism
* support for different scopes including `singleton` and `protoype` as builtin flavors
* support for lazy initialized beans
* support for bean templates
* lifecycle methods ( e.g. `postConstruct` )
* `BeanPostProcessor`'s
* `FactoryBean`'s
* support for hierarchical containers, that inherit beans ( and post processors )
* support for placeholder resolution ( e.g. `${property=}`) in xml
* support for custom namespace handlers in xml
* automatic type conversions and number coercions in xml

# Documentation

For detailed information please visit

* The [Wiki](https://github.com/coolsamson7/inject/wiki) and
* the generated [API Docs](http://cocoadocs.org/docsets/inject/1.0.2/)
* or simply play around with the included playground

# Examples

Let's look at some simple examples.

```Swift
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()
```

One the environment is configured, beans can simply be retrieved via the `getBean()` function.

```Swift
let foo = try environment.getBean(Foo.self)
```
Behind the scenes all bean definitions will be validated - e.g. looking for cyclic dependencies or non resolvable dependencies - and all singleton beans will be eagerly constructed.

Other injections - here property injections - can be expressed via the fluent interface

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

**Injections**

A similar concept as the Java `@Inject` annotations is available that let's you define injections on a class basis.

```Swift
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)
}
```

The protocol `BeanDescriptorInitializer` can be implemented for thus purpose in order to add inejctions to properties. Valid values are:
* `InjectBean` an injection for a specific object type
* `InjectConfigurationValue` an injection of a configuration value

**Scopes**

Scopes determine when and how often a bean instance is created.
* The default is "singleton", which will create an instance once and will cache the value.
* "prototype" will recreate a new instance whenever a bean is requested.

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

Other scopes can be simply added (e.g. session scope ) by defining the implementing class in the current environment.

**Lazy Beans**

Beans that are marked as lazy will be constructed after the first request.

**Factory Beans**

Factory beans are beans that implement a specific protocol and create other beans in turn.

```Swift
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!
```

**Abstract Beans**

It is possible to define a bean skeletton - possibly hiding ugly technical parameters - and let the programmer finsh configuration by adding the missing parts:

```Swift
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
)
```
Usually templates are part of a parent environment to separate technical aspects.

**Bean Post Processor**

Bean Post Processors are classes that implement a specific protocol and are called by the container in order to modify the to be constructed instance.

**Lifecycle Callbacks**

Different protocols can be implemenetd by classes which will be called by the container when an instance is created.
The most important is a `postConstruct` that is called after the instance has been created and all psot processors have been executed.

**Configuration Values**

Every container defines a central registry that maintains configuration values - from different sources - that can be retrieved with an uniform api.

```Swift
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 Configuration**

And for all xml lovers ( :-) ), an xml parser for the original - at least a subset - spring schema.

```xml



























































```

```Swift
var environment = Environment(name: "environment")
var data : NSData = ...
environment
.loadXML(data)
.startup()
```

# Logging

In addition to the injection container, a logging framework has been implemented - and integrated - as well.

Once the singleton is configured

```swift
// 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)
```

the usual methods are provided

```swift
// 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")
```
The `error` and `fatal` functions are called with an `ErrorType` argument. Both functions will emit an message containing the original message, the error representation and the current stacktrace.

Provided log destinations are
* console
* file
* rolling file log (logs get copied every day)
* nslog
* queued log destination

The queueded log destination uses a dispatch queue. As a default a serial queue will be created whose purpose simply is to serialize the entries. In this case ´synchronize: false´ prevents that the console operations are synchronized with a Mutex

## Requirements

- iOS 8.0+
- OSX 10.9
- WatchOS 2.0
- TvOS 9.0
- Xcode 7.0+

# Installation

## Cocoapods

To install with CocoaPods, add `pod 'inject', '~> 1.0.2'` to your `Podfile`, e.g.

```ruby
target 'MyApp' do
pod 'inject', '~> 1.0.2'
end
```

Then run `pod install` command. For details of the installation and usage of CocoaPods, visit [its official website](https://cocoapods.org).

# Limitations

Depending on the specific bean definition, it may be required that the corresponding classes derive from `NSObject`.
This limitation is due to the - missing - `Swift` support for relection. As soon as the language evolves i would change that.

# Roadmap
* support more package managers
* wait for replies :-)

## License

This project is released under the MIT license. See [LICENSE](LICENSE) for details.

[swift-badge]: https://img.shields.io/badge/Swift-3.0-orange.svg?style=flat
[swift-url]: https://swift.org
[mit-badge]: https://img.shields.io/badge/License-MIT-blue.svg?style=flat
[mit-url]: https://tldrlegal.com/license/mit-license