Skip to content
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

Manually created assembly looses it's factory reference #551

Open
AlexeyPoldeo opened this issue Feb 18, 2017 · 10 comments
Open

Manually created assembly looses it's factory reference #551

AlexeyPoldeo opened this issue Feb 18, 2017 · 10 comments

Comments

@AlexeyPoldeo
Copy link

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.

@alexgarbarev
Copy link
Contributor

Can you provide your usage code?

@AlexeyPoldeo
Copy link
Author

AlexeyPoldeo commented Feb 18, 2017

@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;
}

@AlexeyPoldeo
Copy link
Author

AlexeyPoldeo commented Feb 18, 2017

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.

@AlexeyPoldeo
Copy link
Author

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...

@etolstoy etolstoy added the bug label Feb 19, 2017
@etolstoy
Copy link
Contributor

etolstoy commented Feb 19, 2017

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.

@AlexeyPoldeo
Copy link
Author

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.

@jasperblues
Copy link
Member

@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.

@AlexeyPoldeo
Copy link
Author

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?

@jasperblues
Copy link
Member

jasperblues commented Feb 22, 2017

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.

@nicoabie
Copy link

nicoabie commented May 30, 2019

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

5 participants