-
Notifications
You must be signed in to change notification settings - Fork 268
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Circular dependencies are broken #25
Comments
@drodriguez Thanks for the very detailed write-up. . . Would you be interested in sending a pull-request that resolves this issue? I will gladly accept it, and contributors will be (soon) featured on the Typhoon website. If not, I will rectify the issue in the coming days. |
I was trying to come up with a fix yesterday, and, in fact the fix is pretty easy and make those modified tests pass. Then I created another test case because I wasn't sure I covered all the cases (class C depends on children D and E, which refer back to the parent). That assembly makes the populate cache recurse infinitely and I still haven't find out why (I'm trying to get my head around the auto generated method “before advice”, which I think tries to do a toposort, which will not be possible for a non-DAG, if circular dependencies are allowed). |
The 'before advice' is used by the block-style assembly. It is named after the Aspect Oriented Programming terminology. The purpose is just so we can use the Assembly interface, instead of fragile strings for a) Injecting components into each other b) Resolving components at runtime. That way refactoring tools etc work better. Here's what it does: Intercepts all method calls in the Assembly I think the easiest thing to do when working on circular dependencies is ignore all of that, and use the lower-level API for the test cases. . . Once they work, copy the tests to the TyphoonSharedComponentFactoryTests, which would be run against both the block and XML styles. Are you using block-style assembly or XML ? On Jun 30, 2013, at 3:54 PM, Daniel Rodríguez Troitiño [email protected] wrote:
|
We pretend to use block-style assembly. Our religious beliefs forbid us to use XML :/ About the “before advice”, that was what I understood it did. The name (“advice”) was distracting me, because I didn’t know what “advice” it was going to give me. I didn’t know that “advice” was used for this kind of functions in AOP. So, with that in mind, I don’t get why you need to swizzle and unswizzle at this point: [[self class] typhoon_swizzleMethod:sel withMethod:NSSelectorFromString(key) error:nil];
cached = objc_msgSend(me, sel);
if (cached && [cached isKindOfClass:[TyphoonDefinition class]])
{
// Skipped
}
[[self class] typhoon_swizzleMethod:NSSelectorFromString(key) withMethod:sel error:nil]; Why not call directly |
This should definitely be made as simple as possible. . I think you're right. No need to swap back again, just invoke the other method. Jasper Blues On Jun 30, 2013, at 7:59 PM, Daniel Rodríguez Troitiño [email protected] wrote:
|
. . . still broken. . . I haven't had time to look at this yet! |
Fixed in #38. |
I don’t know if the circular dependencies code is WIP or not, but this is one problem we have found trying to use circular dependencies.
The problem can be seen in both tests about circular dependencies. I’m going to choose the first one (
by_reference
) but the other one shows the same behaviour.If you change the test to read like the following code the asserts marked as “Fails” will fail:
The first assert is something that we supposed it will happen when we specified the circular dependency (if A depends on B and B depends on A, the link will be made between two instances, not more). We don’t know if it is what Typhoon is trying to solve.
The second assert is more dangerous, since a chain of modules might be providing the dependencies, and someone might end with an instance of an unexpected type. I know that it has to go through 3 jumps, but I think that creating a bad typed object graph should be avoided.
This is what I see in my debugger when I stop the test just before the first assert is run:
As you can see, the first and the second objects are created with the right types. The third object is also the right type, but instead of being the same object as the first, it is another completely different object. Finally the dependency of that object on ClassB is pointing to itself (!?) which is, obviously, of the wrong class.
From my analysis of what happens this is the sequence of steps that leads to this:
TyphoonSharedComponentFactoryTests.m#157
starts the process asking for a “classA”.-[TyphoonComponentFactory(InstanceBuilder) doPropertyInjection:property:typeDescriptor:]
.-[TyphoonComponentFactory(InstanceBuilder) evaluateCircularDependency:property:instance:]
is made. Since_currentlyResolvingReferences
is empty, the method simply updates the dictionary with@"classB": <ClassADependsOnB: 0x1060052f0>
(this is strange, but seems that this dictionary tells that ClassADependsOnB is resolving "classB" reference?).doPropertyInjection:property:typeDescriptor:
, the code checks that this is not (yet) a circular dependent property, and proceeds with the injection.doPropertyInjection:property:typeDescriptor
, but this time we are creating aClassBDependsOnA
and we are processing thedependencyOnA
property.evaluateCircularDependency:property:instance:
is made. At this point_currentlyResolvingReferences
only has one element (the one created in step 3), so things work more or less like before, and a new pair@"classA": <ClassBDependsOnA: 0x102446650>
is added to the dictionary. I think here lies one of our problems, since we are already resolving "classA" from the start, but we never added it to the_currentlyResolvingReferences
dictionary. This makes the component factory start creating anotherClassADependsOnB
, which is not what we expect.doPropertyInjection:property:typeDescriptor:
, things proceed as in step 4, and we start creating another "classA" reference.doPropertyInjection:property:typeDescriptor:
, trying to create aClassADependsOnB
. This time the things are different, because the circular dependency code starts being exercised.evaluateCircularDependency:property:instance:
is made. This time we have a "classA" in the dictionary, so this instancecircularDependentProperties
dictionary is updated with the pair@"dependencyOnB": @"classB"
. Then we overwrite the_currentlyResolvingReferences
dictionary: the pair we created in step 3 now points to this second instance ofClassADependsOnB
(for reference<ClassADependsOnB: 0x102446a40>
).doPropertyInjection:property:typeDescriptor:
the injection doesn’t get made, and the first time we are able to “escape”doPropertyInjection:property:typeDescriptor:
.-[TyphoonComponentFactory(InstanceBuilder) injectCircularDependenciesOn:]
for the first time. This instance has a circular dependency detected, "dependencyOnB", which references "classB". However, under that name in_currentlyResolvingReferences
we have an instance ofClassADependsOnB
(the one stored in step 9, and before that another one of the same class in step 3). So we end injecting aClassADependsOnB
into adependencyOnB
property. The worse thing, we stored<ClassADependsOnB: 0x102446a40>
in step 9, and we are recovering it, and injecting it in itself.-[TyphoonComponentFactory componentForKey:]
we proceed to remove all objects of_currentlyResolvingReferences
. It doesn’t matter that much at this point, since the first two instances didn’t detect any circular references.There are other smaller bugs related to this code that might generate confusion:
doPropertyInjection:property:typeDescriptor:
, the invocation is made anyway, which I think produces a call to the setter with anil
argument. Some classes might not like that. The invocation should be avoided if possible, or this side-effect should be documented.-[TyphoonComponentFactory(InstanceBuilder) doAfterPropertyInjectionOn:withDefinition:]
is invoked before circular dependencies are setup, so the classes that expects those properties to be set will obtain unexpected results. The invocation of that method should be done after the circular dependencies had been injected, or the behaviour documented.The text was updated successfully, but these errors were encountered: