appsquickly / typhoon Goto Github PK
View Code? Open in Web Editor NEWPowerful dependency injection for Objective-C ✨✨ (https://PILGRIM.PH is the pure Swift successor to Typhoon!!)✨✨
Home Page: https://pilgrim.ph
License: Apache License 2.0
Powerful dependency injection for Objective-C ✨✨ (https://PILGRIM.PH is the pure Swift successor to Typhoon!!)✨✨
Home Page: https://pilgrim.ph
License: Apache License 2.0
Consider the output of a single test case, all of which is from the framework and none from the actual test.
Test Case '-[TyphoonXmlComponentFactoryTests test_returns_component_from_factory_component]' started.
2013-07-23 10:18:11.653 otest[38865:303] Resource: MiddleAgesAssembly.xml
2013-07-23 10:18:11.654 otest[38865:303] Registering: Knight with key: knight
2013-07-23 10:18:11.654 otest[38865:303] $$$$$$$$$$$$$$ Here's the injected property: <TyphoonPropertyInjectedAsCollection: self. name=favoriteDamsels, _values=(
"<TyphoonTypeConvertedCollectionValue: self.textValue=mary, self.requiredType=NSString>",
"<TyphoonTypeConvertedCollectionValue: self.textValue=jane, self.requiredType=NSString>"
)>
2013-07-23 10:18:11.655 otest[38865:303] $$$$$$$$$$$$$$ Here's the injected property: <TyphoonPropertyInjectedAsCollection: self.name=friends, _values=(
"<TyphoonByReferenceCollectionValue: 0x108117870>",
"<TyphoonByReferenceCollectionValue: 0x108102430>"
)>
2013-07-23 10:18:11.655 otest[38865:303] Registering: Knight with key: knightWithCollections
2013-07-23 10:18:11.655 otest[38865:303] Registering: CampaignQuest with key: quest
2013-07-23 10:18:11.656 otest[38865:303] Registering: CampaignQuest with key: anotherQuest
2013-07-23 10:18:11.656 otest[38865:303] Registering: CavalryMan with key: anotherKnight
2013-07-23 10:18:11.656 otest[38865:303] Registering: CavalryMan with key: yetAnotherKnight
2013-07-23 10:18:11.657 otest[38865:303] Registering: NSURL with key: serviceUrl
2013-07-23 10:18:11.657 otest[38865:303] Registering: SwordFactory with key: swordFactory
2013-07-23 10:18:11.657 otest[38865:303] Registering: Sword with key: blueSword
2013-07-23 10:18:11.658 otest[38865:303] Resource: SomeProperties.properties
2013-07-23 10:18:11.658 otest[38865:303] Attaching mutator: <TyphoonPropertyPlaceholderConfigurer: 0x100152e80>
2013-07-23 10:18:11.658 otest[38865:303] Resource: ExceptionTestAssembly.xml
2013-07-23 10:18:11.659 otest[38865:303] Registering: NSURL with key: anotherServiceUrl
2013-07-23 10:18:11.659 otest[38865:303] Registering: NSString with key: aBlaString
2013-07-23 10:18:11.660 otest[38865:303] Resource: CircularDependenciesAssembly.xml
2013-07-23 10:18:11.660 otest[38865:303] Registering: ClassADependsOnB with key: classA
2013-07-23 10:18:11.660 otest[38865:303] Registering: ClassBDependsOnA with key: classB
2013-07-23 10:18:11.661 otest[38865:303] Registering: ClassCDependsOnDAndE with key: classC
2013-07-23 10:18:11.661 otest[38865:303] Registering: ClassDDependsOnC with key: classD
2013-07-23 10:18:11.661 otest[38865:303] Registering: ClassEDependsOnC with key: classE
2013-07-23 10:18:11.662 otest[38865:303] ###### My dependencies have not yet been injected. Here's what I look like: <CampaignQuest: self. imageUrl=(null)>
2013-07-23 10:18:11.662 otest[38865:303] Property setter invocation: <NSInvocation: 0x101d08ca0>
2013-07-23 10:18:11.663 otest[38865:303] $$$$$$$$$$$$$$$$$ My dependencies have been injected. And I know look like this: <CampaignQuest: self. imageUrl=http://www. appsquick.ly/theQuest.jpg>
2013-07-23 10:18:11.663 otest[38865:303] Parsing typeCode: f
2013-07-23 10:18:11.663 otest[38865:303] #################### CavalryMan before properties set
2013-07-23 10:18:11.663 otest[38865:303] Parsing typeCode: c
2013-07-23 10:18:11.664 otest[38865:303] Property setter invocation: <NSInvocation: 0x104d00a40>
2013-07-23 10:18:11.664 otest[38865:303] ############# CavalryMan after properties set
Test Case '-[TyphoonXmlComponentFactoryTests test_returns_component_from_factory_component]' passed (0.011 seconds).
Perhaps a logging framework should be introduced? I'm most familiar with CocoaLumberjack myself, which has static, per file log levels.
After cloning and installing Typhoon from my own repo using CocoaPods I was getting errors when trying to install the dependencies (undefined method `pod' for Pod:Module). I have noticed that the first line of the podspec reads:
pod do |spec|
while in the CocoaPods documentation you can find the first line as
Pod::Spec.new do |spec|
After changing that line in my own repo it seems I no longer have install issues with CocoaPods.
Options:
Is called lifecycle in code assembly and scope in XML assembly. They should match.
TyphoonBlockComponentFactory has a resolveInstanceMethod that allow using the Assembly interface to retrieve components. . . this is more prefereable than resolving by string.
Perhaps it could be enhanced to trampoline non component matching methods back to the assembly class?
This would allow custom factory methods. For example:
[assembly photoClientForId:facebook caching:YES];
Example: Telling Typhoon to inject a property that doesn't exist eats the exception
No meaningful error is thrown. . .
Hi guys,
is there any way to set up to default creating singletons ?
Should ignore lines that begin with a hash ('#') character.
(This currently works - as in doesn't break anything - but wastes memory).
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:
- (void)test_resolves_circular_dependencies_for_property_injected_by_reference
{
ClassADependsOnB* classA = [_circularDependenciesFactory componentForKey:@"classA"];
NSLog(@"Dependency on B: %@", classA.dependencyOnB);
assertThat(classA.dependencyOnB, notNilValue());
// Fails
assertThat(classA, equalTo(classA.dependencyOnB.dependencyOnA));
id dependencyABA = classA.dependencyOnB.dependencyOnA;
// Fails
assertThat([[dependencyABA dependencyOnB] class], equalTo([ClassBDependsOnA class]));
ClassBDependsOnA* classB = [_circularDependenciesFactory componentForKey:@"classB"];
NSLog(@"Dependency on A: %@", classB.dependencyOnA);
assertThat(classB.dependencyOnA, notNilValue());
// Fails
assertThat(classB, equalTo(classB.dependencyOnA.dependencyOnB));
id dependencyBAB = classB.dependencyOnA.dependencyOnB;
// Fails
assertThat([[dependencyBAB dependencyOnA] class], equalTo([ClassADependsOnB class]));
}
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 a ClassBDependsOnA
and we are processing the dependencyOnA
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 another ClassADependsOnB
, 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 a ClassADependsOnB
. 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 instance circularDependentProperties
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 of ClassADependsOnB
(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 of ClassADependsOnB
(the one stored in step 9, and before that another one of the same class in step 3). So we end injecting a ClassADependsOnB
into a dependencyOnB
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 a nil
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.Let's say you have a block based assembly like this:
@interface MiddleAgesAssembly : TyphoonAssembly
- (id)knight;
- (id)cavalryMan;
- (id)defaultQuest;
- (id)anotherKnight;
- (id)yetAnotherKnight;
@end
And you want to have a new assembly with the same definitions except for one. I would like to be able to do something like this:
@interface MiddleAgesAssemblyOverride : MiddleAgesAssembly
@end
@implementation MiddleAgesAssemblyOverride
- (id)knight
{
// Override definition of knight while maintaining the rest of definitions unchanged
}
@end
Typhoon currently does not allow you to do this
When you create chains of circular dependent singletons and mix them with some non singletons components using initializer injection, sometimes you instantiate a singleton several times before registering in the _singletons dictionary. This ends up with different components pointing to different instances of the same singleton.
I can provide a failing test for this sepecific bug.
I also added a couple of sanity checks in TyphoonComponentFactory to avoid registering the same singleton twice. The soution is not specially elegant, but it seems to fix the problem.
The existing organization of source files seems to consists of heavy use of pragma marks as separators, dividing methods into categories such as: initialization/dealloc and private methods.
I'm used to following the step-down rule, wherein methods are arranged such that a method definition is found below, and as close to as possible, the first method that calls it. I might annotate individual methods with comments, if helpful. For example, if overriding -(BOOL)isEqual:, I might make a note that that method is originally defined in NSObject.
The main advantages I see when following the step down rule are less jumping around the methods in an individual class and increased understanding.
Naturally, both these approaches conflict. I've rearranged the code to follow the step down rule myself when trying to understand what a particular class does and have usually left it that way after any subsequent refactorings. I feel that we should decide on one of these approaches going forward and stick to it, for consistency.
I'm also going to try and do a better job going forward matching the style of the code as I'm writing it, so you don't have to go back and fix my out-of-place formatting. I don't think Xcode has anything like this, but does AppCode support project level code style settings? For indentation and code generation and the like? I know AppCode has extensive style settings, but I seem to recall that they are global.
The main argument I'm aware of for doing so is to ensure that all checkouts of the repo are able to successfully build, regardless of if any depended upon cocoapods have had their git repositories deleted or are otherwise unavailable. This obviously does not apply to the Typhoon pod, which is in the same repository as the Tests client; but it does apply to the other test dependencies, OCHamcrest and OCMockito.
In addition, by checking Tests/Pods into VCS, assuming we remember to run pod update
or have a script to run pod update
for us before each commit, we avoid the need for a pod install
before being able to run unit tests with a fresh clone of the repo. We also avoid the need for a pod update
when pulling changes from the repository.
On the other hand, we currently end up with redundant and duplicate changes. If we change a library source file, then run pod update
, then commit, we will see two changes to the same source file; one change will be made to the 'actual' source file under Source, and the other made to the Tests/Pods copy. IIRC specifying a local path for a pod should usually result in an alias to the original file under Pods/, not a copy; I'll have to look into this as well.
EDIT: Okay, after deleting the Pods/ directory and running pod install
, copies of Typhoon library files are no longer made under Pods/ - the original files under Source/ are used instead, as desired. This may or may not be related to having just updated coocapods. Manually deleting the Pods/ directory, and running pod install
after some configuration changes appears necessary.
So, to summarize:
Check Pods/ into VCS?
For:
pod install
under Tests/ when cloning the freshpod update
under Tests/ after git pull
Against:
pod update
before committing (can be mitigated by automatically running as part of builds, see #53)Currently I'm for, especially if I can figure out why copies are being made in Pods/ instead of aliasing to the original source files.
I started Typhoon on my own, but we have some fantastic contributors now. . .
. . to recognize shared ownership, when it reaches 400 stars in GitHub, we'll move it to its own organization page.
Need a proper error to be thrown.
I've done the exercise of building a Core Data Stack using Typhoon and block style definitions. I found it extremely verbose, and I think the main reason is that Typhoon doesn't let you inject or use manually created objects. I understand this is due to the fact that you can't manually create objects in XML, but I don't see any reason why this also should be the case while using blocks.
Here you have the complete code: https://gist.github.com/jgongo/5075937
As you can see, there are four Typhoon definitions just to emulate the following snippet:
NSURL *applicationDocumentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];
NSURL *storeURL = [applicationDocumentsDirectory URLByAppendingPathComponent:@"database.sqlite"];
Also notice the use of hard coded constants in applicationDocumentsDirectories
I think this could be simplified a lot if:
This is handy when there's a dependency to be used by specifically one class.
An id can be attached to the factory to mutate definitions.
So far we have the TyphoonPropertyPlaceholderConfigurer. .
I'm thinking it might be nice to attach a mutator that will tell the assembly to override a given component definition, replacing it with a mock or stub.
This would be useful when you want to do integration testing (test a class along with its collaborators), but configuring a certain part of the assembly to behave in accordance with the test scenario.
Had to upgrade Bamboo server for a client. No time yet to fix Typhoon build.
ant build-publish-dry-run
...
__build.library.ios.simulator:
[exec] 2013-08-31 13:35:01.213 xcodebuild[48077:1207] XcodeColors: load (v10.1)
[exec] 2013-08-31 13:35:01.214 xcodebuild[48077:1207] XcodeColors: pluginDidLoad:
[exec] xcodebuild: error: SDK "iphonesimulator7.0" cannot be located.
BUILD FAILED
/Users/rhgill/Code/Typhoon/build.xml:89: exec returned: 64
Steps to reproduce:
EXPECTED OUTCOME:
ACTUAL OUTCOME
Causes crash.
"We could add a script execution to the Test Target's build phase, that does the 'pod update' as part of the command."
. . should be easier to understand/maintain the code that way.
Typhoon allows the definition of additional type converters like this:
TyphoonComponentFactory* factory = [TyphoonBlockComponentFactory factoryWithAssembly:[ApplicationAssembly assembly]];
[[TyphoonTypeConverterRegistry shared] register:[[HistoryModeTypeConverter alloc] init] forClassOrProtocol:[HistoryMode class]];
It might be nice to allow defining type converters in the assembly itself.
I just started looking at IoC containers for iPhone today and have been testing Typhoon for a bit now.
I noticed it only accepts parameterless methods in the TyphoonAssembly subclass. What is the recommended way of working with things that take blocks? For instance a HTTP request that take blocks for success/failure.
Is the only option to let Typhoon instantiate them and set them afterwards, when returned from the container? Or do i have to create a factory?
XML and Block assembly now allows circular dependencies for properties. Require
Hi There,
Great work so far! I was wondering if the framework supports loading multiple xml config files? The reason I ask is that on a large project, a single config can often become unwieldy, and it often helps to be able to break it down into by functional area or some other organizing principle.
Is this supported, and if not, is it planned?
-Bruce
Ala PicoContainer (see third from the top, How does PicoContainer decide what constructor to use?), this should reduce the number of instances in which you must manually set the selector on the initializer. It's particularly painful to do so, at least in Xcode, as the selectors suggested for autocomplete are not necessarily on the class you're actually trying to initialize.
See:
experimental branch: https://github.com/jasperblues/Typhoon/tree/parameterized-provider
related issue: #42
Hi Jasper,
After the modification in 1.1.3 I was getting another error: the NSManagedObjectModel wan't correctly initialized. So I think the problem is:
-[NSManagedObjectModel initWithContentsOfURL:] seems to be discarding the current instance, and creating and returning a new instance.
In 1.1.3 Typhoon ignores the returned value, so it returns the original instance, uninitialized
In 1.1.2 Typhoon was correctly copying and returning the returned value, but was somehow keeping a reference and releasing the old instance. That was causing an over releasing of that old instance.
Now that I have written it down I feel there's something that doesn't add up... any way, have you taken into account those cases, where initXXX discards the current instance and creates a new one?
Typhoon supports property configurers. The current way to define them is:
TyphoonComponentFactory* factory = [TyphoonBlockComponentFactory factoryWithAssembly:[ApplicationAssembly assembly]];
id <TyphoonResource> configurationProperties = [TyphoonBundleResource withName:@"Configuration.properties"];
[factory attachMutator:[TyphoonPropertyPlaceholderConfigurer configurerWithResource:configurationProperties]];
It might be nice to allow definition of configurers in the Assembly (code or xml).
Since 1.1.0 method injection - injecting via a method without corresponding property raises an error.
I've found coverage reports work better with real source files (as opposed to library).
We could link in using Xcode's sub-project feature.
This scope will:
I was trying to create a Core Data stack using Typhoon and faced the following problem: I had to create the managed object model injecting the URL of the model in the initializer. The URL of the model is typically constructed this way:
NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"CoreDataTest" withExtension:@"momd"];
As far as I know (correct me if I'm wrong) there is no way to create / inject such an object using Typhoon. I could build the main bundle object using Typhoon, but then I have no way of invoking a method on that object to get another object that gets injected in an additional object.
I think the general problem to be solved is to add support for factories: in fact the main bundle is a URL factory, using the method shown above. So maybe we could add something like this to Typhoon:
return [TyphoonDefinition withFactory:[self mainBundle] creator:^(TyphoonCreator *creator) {
creator.selector = @selector(URLForResource:withExtension:);
[creator injectParameterAt:0 withValueAsText:@"CoreDataTest" requiredTypeOrNil:[NSString class]];
[creator injectParameterAt:1 withValueAsText:@"momd" requiredTypeOrNil:[NSString class]];
}];
I've noticed I can have a workaround for this problem using type converters, creating a converter that converts the string "CoreDataTest.momd" to the required NSURL, but this would prevent the creation of a "natural" type converter from strings to NSURLs, as Typhoon only supports a converter for a given type.
What do you think? Am I missing anything?
Hi guys,
is any way to inject/autowire dependencies to storyboard controllers ? I am trying figure out it, but i am lost.
Cheers
Mutator has been replaced by PostProcessor.
to reproduce (xcode 5):
I'm just asking here since I hadn't seen another forum, but please let me know if I'm not in the right place:
Thanks,
Bruce
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.