/Stimpak

Swift Dependency Injection Container

Primary LanguageSwiftApache License 2.0Apache-2.0

Stimpak

Swift Dependency Injection Container

Stimpak is a Dependency Injection Container written in Swift. It is inspired by DryIoc and by Java's CDI.

Please note that this documentation is WIP. If you have any questions, please create an issue.

Examples

Basics

public class Foo {

	public init() {

	}

}

//Create a new container
var container = Container()

//Register Foo as a service using register ...
container.register(Foo.self, { () -> Foo in return Foo.init() })
//... or using the += operator:
container += (Foo.self, { () -> Foo in return Foo.init() })

//Resolve a new instance of Foo
let foo = container.resolve(Foo.self)!

Scoping & Reuse

Stimpak supports scoping in a very flexible way. You can open a scope by calling openScope(_:AnyHashable):

//Create a new container
let container = Container()

let myScope = container.openScope(UUID().uuidString)

You can then dispose the scope using either myScope.dispose() or container.disposeScope(myScope). Any scope can open and dispose new scopes, so you can open and dispose entire scope hierarchies.

The lifecycle of your dependencies depends on their registered Reuse, which is either .transient (default), .singleton or .inScope.

.transient dependencies are never stored in any scope whatsoever,

.singleton dependencies are only stored in the root scope of the container across a scope hierarchy. They are only disposed when the container scope itself is disposed.

.inScope dependencies are bound to the resolving scope. Consider the following example:

var container = Container()
container += (Foo.self, { () -> Foo in return Foo.init() }, Reuse.inScope)

let aScope = container.openScope(UUID().uuidString)
let bScope = aScope.openScope(UUID().uuidString)

let aFoo = aScope.resolve(Foo.self)
let bFoo = bScope.resolve(Foo.self)

aFoo and bFoo are different instances because they have been resolved from within different scopes.

Register & Resolve with arguments

Stimpak supports registering and resolving dependencies using tuples.

container += (Foo.self, { (_ a: String, _ b: String) -> Foo in return Foo(a, b) }, Reuse.inScope )
container.register(Foo.self, { (_ a: String, _ b: String) -> Foo in return Foo(a, b) }, reuse: Reuse.inScope )

container.resolve(Foo.self, ("Bill", "Gates"))

register returns a ServiceResolver<Foo, (String, String)> in this case. You can specify an ArgEqualityComparator as well as an initialization callback for ServiceResolvers. ArgEqualityComparator is important if you do want to prevent that ...

let gates = container.resolve(Foo.self, ("Bill", "Gates"))
let supposedSteve = container.resolve(Foo.self, ("Steve", "Jobs"))

... both return a Foo with first name Bill and last name Gates:

container.register(Foo.self, { (_ a: String, _ b: String) -> Foo in return Foo(a, b) }, reuse: Reuse.inScope )
				.compareArgsBy( { ( a: (String, String), b: (String, String) ) -> Bool in
                      return a.0 == b.0 && a.1 == b.1 })

You can also specify keys of type AnyHashable as a registration argument:

container.register(Foo.self, { (_ a: String, _ b: String) -> Foo in return Foo(a, b) }, reuse: Reuse.inScope, key: "A" )
container.register(Foo.self, { (_ a: String, _ b: String) -> Foo in return Foo(a, b) }, reuse: Reuse.inScope, key: "B" )

container.resolve(Foo.self, ("Bill", "Gates"), "A")

Instantiable-Protocol

Stimpak comes with a simple Protocol called Instantiable. Each struct or class conforming to Instantiable is required to implement a default initializer.

public class Foo : Instantiable {

	public required init() {

	}

}

Foo can now be registered as a dependency like this:

container += (Foo.self, Reuse.singleton)