Search code examples
iosswiftios8xcode6typhoon

Swift Typhoon component property-injection doesn't work


i'm using swift with typhoon and somehow my components don't get injected through property-injection. For simple types like Strings it is working. I provided a simple example which explains the problem. The Output shows the result where serviceB has a null reference to serviceA. All String properties are set properly and no error is thrown. What am i'm doing wrong here?

XCode: 6-beta5 ,Typhoon: 2.1.0

MYServiceA.swift

@objc(MYServiceA) public class MYServiceA : NSObject {

    public var text : String!

} 

MYServiceB.swift

@objc(MYServiceB) public class MYServiceB : NSObject {

    public var text : String!
    public var serivceA : MYServiceA!

}

MYAssembly.swift

public class MYAssembly : TyphoonAssembly {

    public func serviceA() -> AnyObject {
        var definitionBlock : TyphoonDefinitionBlock = {(definition : TyphoonDefinition!) in
            definition.injectProperty("text", with: "some a text")
            definition.scope = TyphoonScopeSingleton
        }
        return TyphoonDefinition.withClass(NSClassFromString("MYServiceA"), configuration: definitionBlock)
    }

    public func serviceB() -> AnyObject {
        var definitionBlock : TyphoonDefinitionBlock = {(definition : TyphoonDefinition!) in
            definition.injectProperty("text", with: "some b text")
            definition.injectProperty("serivceA", with: self.serviceA())
            definition.scope = TyphoonScopeSingleton
        }
        return TyphoonDefinition.withClass(NSClassFromString("MYServiceB"), configuration: definitionBlock)
    }

}

AppDelegate.swift

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

    func application(application: UIApplication!, didFinishLaunchingWithOptions launchOptions: NSDictionary!) -> Bool {

        var assembly : MYAssembly = MYAssembly()
        var factory : TyphoonComponentFactory = TyphoonBlockComponentFactory(assembly: assembly) as TyphoonComponentFactory
        factory.makeDefault()

        var serviceA : MYServiceA = TyphoonComponentFactory.defaultFactory().componentForKey("serviceA") as MYServiceA
        println("MYServiceA")
        println("- instance=\(serviceA != nil)")
        println("- text=\(serviceA.text)")            

        var serviceB : MYServiceB = TyphoonComponentFactory.defaultFactory().componentForKey("serviceB") as MYServiceB
        println("MYServiceB")
        println("- instance=\(serviceB != nil)")
        println("- text=\(serviceB.text)")
        println("- serviceA.instance=\(serviceB.serivceA != nil)")            

        return true
    }
..
}

Output

MYServiceA
- instance=true
- text=some a text

MYServiceB
- instance=true
- text=some b text
- serviceA.instance=false

Solution

  • Typhoon assemblies make good use of the ObjC runtime's dyanmic dispatch features. Assemblies are reflected, and each method intercepted (applying an AOP 'around' advice) so that Typhoon has a blueprint to build each of the components in the app's assembly. However, Swift will try to use static/vtable dispatch where possible (which prevents the required method interception).

    To instruct Swift that dynamic dispatch is required, mark your assembly methods as 'dynamic'.

    Example: (Requires Xcode6 beta 6)

    public class MYAssembly : TyphoonAssembly {
    
        public dynamic func serviceA() -> AnyObject {
            var definitionBlock : TyphoonDefinitionBlock = {
                (definition : TyphoonDefinition!) in
    
                definition.injectProperty("text", with: "some a text")
                definition.scope = TyphoonScopeSingleton
            }
            return TyphoonDefinition.withClass(MYServiceA.classForCoder(), 
                configuration: definitionBlock)
        }
    
        public dynamic func serviceB() -> AnyObject {
            var definitionBlock : TyphoonDefinitionBlock = {
                (definition : TyphoonDefinition!) in
    
                definition.injectProperty("text", with: "some b text")
                definition.injectProperty("serivceA", with: self.serviceA())
                definition.scope = TyphoonScopeSingleton
            }
            return TyphoonDefinition.withClass(MYServiceB.classForCoder(), 
                configuration: definitionBlock)
        }
    
    }