typhoon
typhoon copied to clipboard
Manually created assembly looses it's factory reference
Hello!
I'm newbie to Typhoon and hitting a lot of stupid problems on ObjC...
I prefer to create assemblies manually because using layered architecture and need to create new assembly for each user layer. It's not possible to use automatic assemblies generation because I have to inject some user specific information into the assembly.
Everything works fine until the end of the method. After that any attempt to instantiate new object causes "method not found" crash.
I did some research and found that manually activated assemblies become instances of TyphoonAssemblyAccessor and later loosing reference to fabric property. This property resets to nil because declared as weak. It makes the assembly unusable anywhere outside the method scope where is was created because it immediately looses it's fabric.
I've figured out that the only way to keep this reference alive is to patch the sources (make it strong) or to make the assembly default.
This definitely is something that I do not want to do.
Please help.
PS: Please do not suggest to switch to Swift or use plist assemblies creation.
Can you provide your usage code?
@interface AppAssembly : TyphoonAssembly
+ (AppAssembly *) create {
AppAssembly *assembly = [[AppAssembly new] activated];
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
[assembly inject:[userDefaults rac_channelTerminalForKey:@"currentUserId"] withSelector:@selector(currentUserIdDefault)];
return assembly;
}
For test purposes I've put [AppAssembly create] into viewDidLoad and inside this method everything works fine. But In the other method (button press callback) I can see reference to AppAssembly since it's retained, but fabric and assembly properties are nil.
I'm using current version of Typhoon installed using Pods.
When I checking assemblies created in PocketForecast sample app, I see that it has TyphoonBlockComponentFactory instance class. Mine's are TyphoonAssemblyAccessor.
I'm really confused...
Hi, @AlexeyPoldeo ! I think that the problem is in the way you are trying to use Typhoon.
I prefer to create assemblies manually because using layered architecture and need to create new assembly for each user layer.
When you create multiple assemblies manually (by calling [[AppAssembly new] activated]
each time), you don't share dependency graph between them. It means, that if you have a singleton object in AppAssembly
, this instance won't be accessible in your second assembly.
It's not possible to use automatic assemblies generation because I have to inject some user specific information into the assembly.
Well, this is possible but this approach doesn't belong to best practices. Consider assemblies as simple factories without any internal state. They are just proxies to access an underlying pool of created dependencies.
With all my respect, but I see too many limitations in comparison to Dagger 2. It's possible to name it as features, but they are not...
Senior developers, who are smart enough to learn how to use Typhoon, do not need to put configuration to plist file to simplify their work. They want to get some tool to manage complicated object graphs and layer fabrics.
@AlexeyPoldeo If you want to create assemblies manually this is no problem, however the way you tried is not the correct way for Typhoon. What you need to do is:
- Create an assembly, such as your top-level layer. Let's say it needs a collaborating assembly, network components.
##Example
@interface UIAssembly : TyphoonAssembly
// Typhoon will automatically proxy the two collaborating assemblies
// (NetworkComponents and TyphoonAssembly<PersistenceComponents>) here.
@property(nonatomic, strong, readonly) NetworkComponents* networkComponents;
// Collaborating assemblies can be backed by a protocol. We declare
// type using TyphoonAssembly<FactoryProtocol> syntax to tell Typhoon
// that this is a collaborating assembly. In the app's own classes no further
// coupling to Typhoon is necessary, and we may declare a property as type
// id<FactoryProtocol>, avoiding your classes from being aware of Typhoon.
@property(nonatomic, strong, readonly) TyphoonAssembly<PersistenceComponents> persistenceComponents;
// Local components that require components from collaborating assemblies ...
- (RootViewController *)rootViewController;
- (SignUpViewController *)signUpViewController;
- (StoreViewController *)storeViewController;
@end
We declare a property of type NetworkComponents, like this: @property(nonatomic, strong, readonly) NetworkComponents* networkComponents;
And then to activate you just call:
UIAssembly *uiAssembly = [[UIAssembly new] activate];
Typhoon will automatically wire in the NetworkComponents
assembly. If you wish to override this with a sub-class or anything else that fulfills the contract you can use:
UIAssembly *uiAssembly = [[UIAssembly new]
activateWithCollaboratingAssemblies:@[
[TestNetworkComponents new],
[PersistenceComponents new]];
Typhoon for Objective-C is nothing like Dagger 2. The way that it works is:
- Non-activated assemblies contain recipes for building components. These can be modularized and reference each other.
- Typhoon gathers the recipes into a single factory that uses these to emit built components. Activating the assembly gives you a proxy to this factory.
This is a very Objective-C-esque approach, which at the time Typhoon was written is what people were after.
More info on assembly modularization is available here: https://github.com/appsquickly/Typhoon/wiki/Modularizing-Assemblies
Typhoon for Swift is somewhat more like Dagger in that it uses compile-time rather than runtime processing.
Thank you for trying Typhoon and let me know if you need further assistance setting up.
Thank you for details, Jasper, but I didn't got what I did wrong...
I've created assembly and it's fabric died after finishing the enclosing method.
Example:
@property (nonatomic) TestAssemply *assembly;
-
(void) method1 { self.assembly = [[TestAssembly new] activate]; /* It's OK! */ }
-
(void) method2 { self.assembly - unusable because start responding "unknown selector" to any object creation request. }
It it bug or it's by design?
Can you send me a sample project? Sounds like a bug or user error of some sort. Likely the latter. Your code above looks fine - sample project will help me understand better.
@jasperblues @alexgarbarev @etolstoy I'm attaching a simple project where I believe this issue is replicated. It contains a test that asserts if the assembly injected by property is the same as the assembly passed to the initializer. (using the former works as expected) BugFactory.zip
Thanks in advance.