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
- Host: GitHub
- URL: https://github.com/coolsamson7/inject
- Owner: coolsamson7
- License: mit
- Created: 2016-07-18T13:08:28.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2017-04-12T12:26:12.000Z (about 9 years ago)
- Last Synced: 2025-08-13T11:30:10.875Z (10 months ago)
- Topics: injection, injection-container, logger, swift, xml-parser
- Language: Swift
- Size: 329 KB
- Stars: 4
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# inject
[](https://developer.apple.com/swift)
[](https://travis-ci.org/coolsamson7/inject)
[](https://github.com/coolsamson7/inject)
[](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