Search code examples
swiftstoryboarduistoryboardtyphoon

Injecting a view controller from a storyboard using Typhoon and Swift


What is the proper way to inject a UIViewController instantiated from a UIStoryboard using Typhoon and swift?

I can't find any clear examples of this in either of the sample apps, and when I translate the Objective C code from the documentation it throws an exception.

Below is the code from my AppAssembly:

public dynamic func loginViewController() -> LoginViewController {
    return TyphoonDefinition.withClass(LoginViewController.self) {
        (definition) in

        definition.injectProperty("socialClient",with:self.coreComponents.socialClient())
        //definition.scope = TyphoonScope.Singleton
    } as LoginViewController
}

And this is the exception that gets thrown:

0x10636ca1c:  jne    0x10636ca10               ; swift_dynamicCastClassUnconditional + 48
0x10636ca1e:  leaq   0x36b3d(%rip), %rax       ; "Swift dynamic cast failed"
0x10636ca25:  movq   %rax, 0xb4a2c(%rip)       ; gCRAnnotations + 8
0x10636ca2c:  int3   
0x10636ca2d:  movq   %rdi, %rax
0x10636ca30:  popq   %rbp
0x10636ca31:  retq   
0x10636ca32:  nopw   %cs:(%rax,%rax)

The "Swift dynamic cast failed" leads me to believe that what was possible using Objective C is just not possible using Swift.

Has anyone been able to make this work? Any assistance would be greatly appreciated. The library looks really nice and I really want to use it.


Solution

  • The following rules apply using Typhoon in both Swift and Objective-C:

    • At build-time your assembly interfaces return recipes for assembling an object instance. This includes configuration along with any collaborating components.
    • At run-time the assembly interface returns components built according to those recipes.

    However there's an important difference between Typhoon Objective-C and Typhoon Swift:

    Objective-C:

    • In Objective-C we recommend that your assembly interfaces declare to return the type that will be built, as this provides better code-completion and avoids unnecessary casting.
    • In Objective-C, you can have any of your assembly interfaces 'pose' in front of a TyphoonComponentFactory simply by casting it to one of your assembly interfaces.

    Swift:

    • Unfortunately in Swift your assembly methods must return type AnyObject. Swift's strict type-checking won't allow otherwise. This is shown in the Quick Start guide for Swift.
    • In Swift its not possible to cast a TyphoonComponentFactory to one of the assembly interfaces. However its still possible to inject the assembly into a component as follows:

    Injecting the Assembly:

    dynamic func appDelegate() -> AnyObject {
        return TyphoonDefinition.withClass(AppDelegate.self) {
            (definition) in
    
            //The type of this property can be TyphoonComponentFactory or any of your 
            //assembly interfaces. 
            definition.injectProperty("assembly", with: self)
        }
    }
    

    Fixing your crash:

    Therefore to fix the crash by obeying the above conditions, change your code to the following:

    Change your code to the following:

    public dynamic func loginViewController() -> AnyObject {
        //etc
    }
    

    Resolving Components from Storyboard:

    As long as your start Typhoon using plist integration and include the usual UILaunchStoryboardName and UIMainStoryboardFile, then Typhoon will ensure your storyboard is an instance of TyphoonStoryboard, this works like a normal storyboard with the added behavior that dependencies are injected according to rules in your assembly.

    Documentation for this feature is here.

    The following features will be coming soon: