Skip to content

Container

Andrey Pleshkov edited this page Apr 17, 2019 · 16 revisions

Dependency Injection (DI) containers or Inversion of Control (IoC) containers allow to make an object graph, automate objects composition and manage their lifecycle.

Saber uses the term "service" for "object".

There're no limitations on how many containers and their services you want to declare.

To define a new container add a protocol and annotate it:

// @saber.container(AppContainer)
// @saber.scope(App)
protocol AppContaining {
}

After running Saber you get a AppContainer.saber.swift file with a class named AppContainer, which conforms to AppContaining:

// NOTE: `internal` is used by default and can be changed via Saber configuration
internal class AppContainer: AppContaining {

    internal init() {
    }

}

To add a simple service just declare a class/struct and set its scope:

// @saber.scope(App)
class Foo {
}

Run Saber and see how the container file has changed:

internal class AppContainer: AppContaining {

    internal init() {
    }

    // NOTE: that's the same `internal`
    internal var foo: Foo {
        let foo = self.makeFoo()
        return foo
    }

    // NOTE: a factory is always private for every service
    private func makeFoo() -> Foo {
        return Foo()
    }

}

Now AppContainer manages Foo:

  • Foo can be retrieved outside the container via the foo property
  • Foo can be used to resolve dependencies

Let's add another service named Bar and set Foo as its dependency:

// @saber.scope(App)
class Bar {

    // @saber.inject
    init(foo: Foo) {
        // ...
    }
}

Generated:

internal class AppContainer: AppContaining {

    internal init() {
    }

    internal var foo: Foo {
        let foo = self.makeFoo()
        return foo
    }

    internal var bar: Bar {
        let bar = self.makeBar()
        return bar
    }

    private func makeFoo() -> Foo {
        return Foo()
    }

    private func makeBar() -> Bar {
        return Bar(foo: self.foo) // injected!
    }

}

Now AppContainer manages Bar too and solves its dependency.

Saber supports not only initializer, but also property and method (or setter) injections.

All container annotations:

  • @saber.container(Name): name of a generated container class (e.g. @saber.container(AppContainer)) and its filename: %Name%.saber.swift (e.g. AppContainer.saber.swift).
  • @saber.scope(ScopeName): name of a container scope (e.g. @saber.scope(App)). Every service is attached to a container via the same annotation.
  • @saber.threadSafe: [optional] makes a container thread-safe.
  • @saber.dependsOn(OneContainer, AnotherContainer, ...): [optional] organizes a multiple inheritance, comma-separated list of containers to be inherited from.
  • @saber.externals(A, B, ...): [optional] comma-separated list of container externals (e.g. @saber.externals(Env, Analytics)).
  • @saber.imports(A, B): [optional] comma-separated list of additional modules to import inside a generated container file (e.g. @saber.imports(UIKit, Alamofire)). Foundation is added by default.
Clone this wiki locally