I am using Typhoon for dependency injection. But I have a problem. Occasionally, I get the following exception:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'No component matching id 'nemIdStoryboard'.'
The code, where this exception occurs, looks like this:
class PaymentApproveViewController : UIViewController {
var assembly : ApplicationAssembly!
//...
private func signPayment() {
let storyboard = assembly.nemIdStoryboard()
let controller = storyboard.instantiateInitialViewController() as NemIDViewController
//...
}
}
And my assembly code looks like this:
public dynamic func rootViewController() -> AnyObject {
return TyphoonDefinition.withClass(RootViewController.self) {
$0.injectProperty("assembly", with: self)
}
}
public dynamic func paymentApproveViewController() -> AnyObject {
return TyphoonDefinition.withClass(PaymentApproveViewController.self) {
$0.injectProperty("assembly", with: self)
}
}
public dynamic func nemIdStoryboard() -> AnyObject {
return TyphoonDefinition.withClass(TyphoonStoryboard.self) {
$0.useInitializer("storyboardWithName:factory:bundle:") {
$0.injectParameterWith("NemID")
$0.injectParameterWith(TyphoonBlockComponentFactory(assembly:self))
$0.injectParameterWith(nil)
}
}
}
I have the exact same code for injecting the assembly into my RootViewController, which also retrieves a storyboard and instantiates a view controller the same way as above. But this never fails.
I cannot identify any reason to why this should fail occasionally, and not be consistent. But I have a feeling, that the assembly might not be properly initialized in Swift code using optional types. Could that be the case? Or can you suggest anything I can do to make this code work?
Edit:
I have printed out TyphoonComponentFactory._registry
from rootViewController and from PaymentApproveViewController. The interesting result is that:
_registry
contains a list of all typhoon definitions in my ApplicationAssembly. That is, all objects and storyboard definitions_registry
contains all objects except my five storyboards. This is the reason that TyphoonComponentFactory.componentForKey throws an exception when it does not find the storyboard.BTW: The address of the assembly is different in rootViewController
and PaymentApproveViewController
. So it is two different assemblies that are injected. Can this be avoided, or is it expected behavior?
It looks like the problem is with:
$0.injectParameterWith(TyphoonBlockComponentFactory(assembly:self))
This is instantiating a new assembly using only the definitions found in the current one, and skipping any collaborating assemblies. You actually want:
$0.injectParameterWith(self)
In Objective-C its possible to cast any TyphoonAssembly
to TyphoonComponentFactory
(at runtime your assemblies are just an instance of TyphoonComponentFactory
posing as an assembly in any case). With Swift's strict type checking this isn't possible, so instead Typhoon 3.0 has the TyphoonAssembly
class conforms to the TyphoonComponentFactory
protocol, so if you need any methods from this interface they are available. Eg:
assembly.componentForType(SomeType.self)
Summary
TyphoonAssembly
not TyphoonComponentFactory
.TyphoonAssembly
conforms to the TyphoonComponentFactory
protocol.