Hi,
I've copied the code from the circular dependency example and added a few things in the classes, didn't modify anything in the dependency registration code.
Classes and protocols look like this:
protocol ParentType: AnyObject {
func getMyName() -> String
func getMyChildName() -> String
}
protocol ChildType: AnyObject {
func getMyName() -> String
func getMyParentName() -> String
}
class Parent: ParentType {
let child: ChildType?
let name = "John"
init(child: ChildType?) {
self.child = child
}
func getMyName() -> String {
return name
}
func getMyChildName() -> String {
return child!.getMyName()
}
}
class Child: ChildType {
weak var parent: ParentType?
let name = "John Jr."
func getMyName() -> String {
return name
}
func getMyParentName() -> String {
return parent!.getMyName()
}
}
Dependency configuration code looks like this (unchanged from example):
let container = Container()
container.register(ParentType.self) { r in
Parent(child: r.resolve(ChildType.self)!)
}
container.register(ChildType.self) { _ in Child() }
.initCompleted { r, c in
let child = c as! Child
child.parent = r.resolve(ParentType.self)
}
The above code is in my AppDelegate's "application:didFinishLaunchingWithOptions" function.
Right after the registration code I added this little testing code:
let parent = container.resolve(ParentType.self)!
let child = container.resolve(ChildType.self)!
print(parent.getMyName())
print(child.getMyName())
print(parent.getMyChildName())
print(child.getMyParentName())
The output is this:
John
John Jr.
John Jr.
fatal error: unexpectedly found nil while unwrapping an Optional value
The error occurs on this line:
return parent!.getMyName()
The weird thing is that I placed a breakpoint on that line and this is what happens:
self, it looks correctly initialised, the parent property looks correct (a reference to the parent instance)parent property is nilAm I doing anything wrong with this circular dependency? (This code is in an "empty" single view iOS app, only with Swinject added as a dependency via Carthage).
XCode version 7.2.1
Swinject version 1.1 installed via Carthage
Hi @Gabriel-Lacatus, the problem is caused because parent property of Child is defined as a weak property.
Let's name the instances like following.
let parentA = container.resolve(ParentType.self)!
let childB = container.resolve(ChildType.self)!
Here the parent of childB is a different instance from parentA. Since parent property of childB is weak, it is set to nil after childB instance is created.
On the other hand, child property of Parent is a strong property. The instance of the child of parentA is different from childB, but the child instance is held by parentA.
Regarding the concept of circular dependency, parentA's child's parent is parentA, which is the same instance.
If ParentType is registered as below,
container.register(ParentType.self) { r in
Parent(child: r.resolve(ChildType.self)!)
}
.inObjectScope(.None)
parentA's child's parent and parentA are different instances.
OK, but since you recommend that the property is weak to avoid a memory leak then how is this construction usable? Do I need to instantiate everything in .Container scope (i.e. Singleton?)
If you need the direct access to a child instance, you can add var child: ChildType? { get } to ParentType protocol without using .Container scope. If your app needs only single instances of ParentType and ChildType, you can use .Container scope. The usage depends on the situation.
I don't need direct access from outside to the parent property of a ChildType object. I was just trying to use the injected parent property within the ChildType object. How do I use that property if it's nil (I also don't understand why it's still nil - shouldn't it have been injected with a proper instance?
Trying to wrap my head around the concepts... is it the case that when I request an instance of ChildType, the initCompleted closure for ChildType is not called because the resolving is not done inside a registration?
In other words the resolving of a ChildType here:
Parent(child: r.resolve(ChildType.self)!)
is somehow different from this one:
let child = container.resolve(ChildType.self)!
?
initCompleted is called. You can confirm the call by logging in initCompleted.
Studying about weak var might be helpful for you. For example, the following code prints "true".
weak var parent: Parent? = Parent(child: nil)
print(parent == nil)
For more details, please refer to:
http://stackoverflow.com/questions/24011575/what-is-the-difference-between-a-weak-reference-and-an-unowned-reference
So if a weak reference can become nil automatically this means our injected parent property can become nil without our control. What is the child supposed to do when it needs to use its parent dependency which became nil in the meantime?
@Gabriel-Lacatus, you pointed out the very interesting point of the usage of DI container with the circular dependency. Your question is very smart馃憤
I'm thinking how to answer to your question. In the meantime, do you have any good solution? If you have a good idea, I would like to hear it to get better understanding of the concept of DI.
My situation is such that my components that depend on each other should only exist in one instance. Therefore I register them at .Container level and this seems to help with the weak referencing. However, I can just guess why and I haven't looked into this in detail, I'm no Swift expert, I just started learning a month ago.
In a sense I see that circular dependencies are the exact thing that weak referencing is trying to avoid, because of the garbage collection. In Swift's opinion it's not OK (or not recommended) that we have strong circular references.
An easy to give "answer" would be to just re-architecture one's solution so that one doesn't have the need for circular references. But I think this is not always a possible thing. I will think about this some more.
Thank you for sharing your experience and giving me your valuable idea馃槂 An instance referenced by a weak property must be owned by another. It's simpler not to have circular references if possible, but I agree that it's not possible sometimes.
I really appreciate you keep thinking about solutions more. Your idea is helpful for me and users of Swinject. I would like to hear that. Thank you!
Thanks Gabriel for sharing your idea. Feel free to reopen this issue if you get more idea to share馃挕