Skip to main content
Version: Canary 🚧

Modules

Syringe introduces an intuitive module DSL that makes dependency management a breeze.

The following functions are part of the module DSL:

  • factory { Module -> T } - Provides a factory that returns a new instance on every resolve
  • singleton { Module -> T } - Provides an instance that is retained once it is created
  • module.get() - Resolves a previously registered dependency from this module. May be used with any number of parameters.

Creating a Module​

Let's create a new module

let yourModule = module {
// Dependencies go here
}

Let's add a singleton of type Service to our module:

class Service {
...
}

let yourModule = module {
singleton { _ in Service() }
}

Note that the same syntax applies to factories:

let yourModule = module {
singleton { _ in Service() }
factory { _ in Int.random(0..<5) }
}

Resolving Dependencies​

The dependencies can now be resolved:

class View {
let service: Service = inject()
}

Dependencies may also resolve other dependencies inside of the module:

class User {
...
}

class Service {
let user: User

init(user: User) {
self.user = user
}
}

let yourModule = module {
singleton { module in Service(user: module.get()!) }
// or
singleton { _ in Service(user: $0.get()!) }
factory { _ in User() }
}

Resolved dependencies will be nil when not previously registered:

class Repository {
let storage: Storage

init(storage: Storage) {
self.storage = storage
}
}

let yourModule = module {
singleton { module in Repository(storage: module.get()!) }
}
danger

Dependencies are Optional types.

If they are unfound the logger will print possible issues and the resolved dependency will be nil.

Passing Parameters​

It is also possible to pass parameters during resolve:

class Counter {
let value: Int

init(x: Int, y: Int) {
self.value = x + y
}
}

let yourModule = module {
factory { _ in Counter(x: $0, y: $1) }
}

class View {
init() {
var counter: Counter = inject(1, 2)!
}
}
Singletons

Passing parameters to singletons works only for the first time. Once singletons are created parameters are ignored on subsequent resolves.

Late-resolving​

Sometimes it is inevitable to have a circular dependency. Syringe allows you to break the circle and late-resolve certain dependencies:

public class Parent {
public let child: Child

public init(child: Child) {
self.child = child
}
}

public class Child {
public var parent: Parent!

init() {

}
}

let testModule = module {
singleton { module in Parent(child: module.get()) }
.onInit { module, other in
// Only parameter is the target dependency as Any?
guard let parent: Parent = other as? Parent else { fatalError() }
let child: Child = module.get()!
child.parent = parent
}
singleton { _ in Child() }
}

injectSyringe {
modules {
testModule
}
}

Making use of Polymorphism​

Sometimes you want your dependency to be the base type of something, while the returned object is a specialisation of that. Syringe allows that via regular Swift typecasts.

protocol Repository {
func load()
}

class RepoImpl: Repository {
func load() {
...
}
}

class RepoMock: Repository {
func load() {
...
}
}

let yourModule = module {
// Use mock for testing
#if TESTING
singleton { _ in RepoMock() as Repository }
// Use live for anything else
#else
singleton { _ in RepoImpl() as Repository }
#endif
}

class View {
let repo: Repository = inject()!

init() {
repo.load()
}
}