Circular dependencies are dependencies of instances that depend on each other. To define circular dependencies in Swinject, one of the dependencies must be injected through a property.
Assume that you have Parent
and Child
classes depending on each other. Parent
depends on ChildProtocol
through its initializer, and Child
on ParentProtocol
through a property. The back-reference from Child
to ParentProtocol
is a weak property to avoid a memory leak.
protocol ParentProtocol: AnyObject { }
protocol ChildProtocol: AnyObject { }
class Parent: ParentProtocol {
let child: ChildProtocol?
init(child: ChildProtocol?) {
self.child = child
}
}
class Child: ChildProtocol {
weak var parent: ParentProtocol?
}
The circular dependencies are defined as below:
let container = Container()
container.register(ParentProtocol.self) { r in
Parent(child: r.resolve(ChildProtocol.self)!)
}
container.register(ChildProtocol.self) { _ in Child() }
.initCompleted { r, c in
let child = c as! Child
child.parent = r.resolve(ParentProtocol.self)
}
Here the injection to the parent
property of Child
must be specified in the initCompleted
callback to avoid infinite recursion.
Similarly, assume that you have the following classes depending on each other, each via a property:
protocol ParentProtocol: AnyObject { }
protocol ChildProtocol: AnyObject { }
class Parent: ParentProtocol {
var child: ChildProtocol?
}
class Child: ChildProtocol {
weak var parent: ParentProtocol?
}
The circular dependencies are defined as below:
let container = Container()
container.register(ParentProtocol.self) { r in
let parent = Parent()
parent.child = r.resolve(ChildProtocol.self)!
return parent
}
container.register(ChildProtocol.self) { _ in Child() }
.initCompleted { r, c in
let child = c as! Child
child.parent = r.resolve(ParentProtocol.self)
}
Here both or either of the depending properties must be specified in the initCompleted
callback to avoid infinite recursion.
Not supported. This type of dependency causes infinite recursion.
When resolving circular dependencies one of the factory methods (one containing resolution of circular dependency) might be invoked twice. Only one of the resulting instances will be used in the final object graph but in some cases this can be problematic - particularly when there are side effects of factory invocation, such as
- time consuming operations
- interactions with resolved dependencies
You can avoid duplicate invocation by resolving both parts of the dependency cycle inside initCompleted
closures, for example refactoring
container.register(ParentProtocol.self) { r in
let parent = Parent()
parent.child = r.resolve(ChildProtocol.self)!
return parent
}
to
container.register(ParentProtocol.self) { _ in Parent() }
.initCompleted { r, p in
let parent = p as! Parent
parent.child = r.resolve(ChildProtocol.self)!
}