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

Swift : Prevents instrumentation of Typhoon methods by static/vtabling them. #243

Closed
twyneed opened this issue Aug 21, 2014 · 12 comments
Closed

Comments

@twyneed
Copy link

twyneed commented Aug 21, 2014

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
@twyneed
Copy link
Author

twyneed commented Aug 21, 2014

FYI: I've send a test-project to [email protected] which contains the code from above

@alexgarbarev
Copy link
Contributor

Hi @ robbiebubble. Thanks for feedback, very usefull information for us.
I investigated the problem, and found that method swizzling is not working properly for swift classes.
When you calling swift method in swift - it is not sending message, like objective-c but something like vtable in C++
See the example below:

func funcA() -> String! {
    return "funcA"
}

func funcB() -> String! {
   return "funcB"
}

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

    var methodA: Method = class_getInstanceMethod(object_getClass(self), Selector.convertFromStringLiteral("funcA"))
    var methodB: Method = class_getInstanceMethod(object_getClass(self), Selector.convertFromStringLiteral("funcB"))
    method_exchangeImplementations(methodA, methodB)

    println("funcA = \(self.funcA())")

    let key = "funcA"
    println("funcA = \(self.valueForKey(key))")

    return true
}

The output will be

func = funcA
func = funcB

This example shows that swizzling works, but only when sending message, for example when calling valueForKey(key)
And swizzling doesnt works when direct method calling. Seems that it works like C function calls.

This is the reason why Typhoon didn't work properly for swift-based assemblies. We will work to fix that (I already have few ideas). But for now, use valueForKey(key) approach.

@twyneed
Copy link
Author

twyneed commented Aug 21, 2014

@alexgarbarev
Thx for (fast) investigation!
Works like a charm :)

MYAssembly.swift

...
 definition.injectProperty("serivceA", with: self.valueForKey("serviceA"))
...

Output

...
- serviceA.instance=true
...

@jasperblues
Copy link
Member

In the latest beta of Swift, I think you have to do this:

  • Mark all of your methods 'dynamic'

. . otherwise they're candidates for inline or 'performance enhancing' even if the assembly is marked '@objc'. Thanks to @hsuanyat for this advice.

Add the word 'dynamic' after 'func'

@jasperblues
Copy link
Member

@jasperblues
Copy link
Member

@RobbieBubble,

@alexgarbarev and I have just been having a discussion:

We think:

  • At the present time, the best way to use Typhoon with Swift, is to create your assembly in Objective-C (the rest of your app can be Swift)

. . . Typhoon assemblies make good use of the ObjC runtime's dyanmic dispatch features, which current versions of Swift will often second guess.

I you're writing a serious app, and have a deadline we recommend this approach. Meanwhile, if you're doing research, then your feedback is incredibly valuable.

@twyneed
Copy link
Author

twyneed commented Aug 22, 2014

I you're writing a serious app, and have a deadline we recommend this approach

As usual, thats the case. To be honest i didn't expected to get that incredible (fast + working) feedback to my questions regarding typhoon (i know this won't always be case because all of you probably have to do things beside). But because of that and as long as i got a fallback-solution (e.g. moving the assemblies to an objective-c class which isn't a big deal) i will stick on the "swift-way" and try to get as far as possible.

@jasperblues
Copy link
Member

👍 That will be very helpful for us. . It depends how things evolve with Swift, but we think Swift-style assemblies will work somewhat differently to the Objective-C way.

Aleksey did a couple of hours research yesterday and found:

  • Collaborating assemblies only works if you do this:
    var anotherAssembly : AnyObject!

    override init() {
        super.init()
        self.anotherAssembly = TyphoonCollaboratingAssemblyProxy()
    }

. . normally Typhoon will detect this and do it for you, but it doesn't happen in Swift!

  • Runtime arguments - one of the best features of Typhoon - don't work at all :(

https://github.com/typhoon-framework/Typhoon/wiki/Types-of-Injections#injection-with-run-time-arguments

@jasperblues jasperblues changed the title Swift Typhoon component property-injection doesn't work Swift : Prevents instrumentation of Typhoon methods by static/vtabling methods. Aug 26, 2014
@jasperblues jasperblues changed the title Swift : Prevents instrumentation of Typhoon methods by static/vtabling methods. Swift : Prevents instrumentation of Typhoon methods by static/vtabling them. Aug 26, 2014
@jasperblues
Copy link
Member

Using dynamic fixes this, as shown in 'Swift Quick Start'

@bradfeehan
Copy link

Using dynamic fixes this, as shown in 'Swift Quick Start'

@jasperblues: does this include the following?

Runtime arguments - one of the best features of Typhoon - don't work at all :(

@jasperblues
Copy link
Member

@bradfeehan runtime args should work AFAIK

Example

Given:

    public dynamic func wanderingKnight(homeFort : Fort) -> AnyObject {

        return TyphoonDefinition.withClass(Knight.self) {
            (definition) in

            definition.useInitializer("initWithQuest:") {
                (initializer) in

                initializer.injectParameterWith(self.defaultQuest())
            }
            definition.injectProperty("homeFort", with: homeFort)
        }
    }

Then:

    internal func test_injects_runtime_args() {

        let assembly = SwiftMiddleAgesAssembly()
        TyphoonAssemblyActivator.withAssembly(assembly).activate()

        let fort = Fort()
        let knight = assembly.wanderingKnight(fort) as Knight
        println(knight.description())
        XCTAssertTrue(knight.isKindOfClass(Knight.self))
        XCTAssertNotNil(knight.homeFort)

    }

@jasperblues
Copy link
Member

@bradfeehan Also note this issue is a duplicate of #286

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

No branches or pull requests

4 participants