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

Loading object from assembly crashes only in release mode #310

Closed
sahara108 opened this issue Jan 22, 2015 · 14 comments
Closed

Loading object from assembly crashes only in release mode #310

sahara108 opened this issue Jan 22, 2015 · 14 comments

Comments

@sahara108
Copy link

I want to test the TyphoonScope, so that in your swift example PocketForecast, I add this method in ApplicationAssembly:
public dynamic func customObj() -> AnyObject { return TyphoonDefinition.withClass(Temperature.self) { (definition) in definition.scope = .LazySingleton definition.useInitializer("initWithCelciusString:") { (initializer) in initializer.injectParameterWith("1000") } } }
Now what I want is when I perform an action I will call my customObj and check its address.
So, in RootViewController's showCitiesListController, I call let tempObj = self.assembly.customObj(). (I should create a button to do that, but forgive me, I just want a quick test).
This code runs well in debug mode but not in release mode and throws exception [NSUserDefaults copyWithZone:]: unrecognized selector sent to instance 0x7fab98e106f0.
Also I really want to know why it crashes inside NSUserDefaults while I don't make any call to NSUserDefaults class.
Thanks.

@jasperblues
Copy link
Member

Unfortunately Swift can perform some optimizations of UIKit or NSFoundation classes resulting in methods being inlined or invoked in a static fashion, even if the method is overridden and marked 'dynamic'. It prevents runtime method invocation that is required by Typhoon.

The workaround is to provide a custom initializer instead of using one of the Foundation ones. Eg MyViewController.initWithNibName:bundle can cause the same problem, but using a custom initializer won't. Although possible to workaround, this is quite a limitation and we want to make this more clear in the Typhoon docs.

Meantime, we'll need to see some more code to help.

Related #286 . . we've raised a Radar bug about this.

@sahara108
Copy link
Author

If I use return TyphoonDefinition.withClass(Temperature.self) { (definition) in definition.useInitializer("init") }
and overwrite the init method, it works well in both debug and release

@jasperblues
Copy link
Member

Oops. Didn't see you were using "one of your own" classes. Did your try marking initWithCelciusString as dynamic?

@jasperblues
Copy link
Member

ie, you can just mark the method as dynamic, like this:

    public dynamic convenience init(celciusString : String) {
        let fahrenheit = NSDecimalNumber(string: celciusString)
            .decimalNumberByMultiplyingBy(9)
            .decimalNumberByDividingBy(5)
            .decimalNumberByAdding(32)
        self.init(temperatureInFahrenheit : fahrenheit)
    }

Having been marked as dynamic, it will use dynamic dispatch and thus allow reflective invocation, as required by Typhoon / meta-programming.

NB: There are some edge cases (#286) where this doesn't work with UIKit/Foundation classes.

@sahara108
Copy link
Author

Yes, I tried mark the method as dynamic but it still crashes. In my case, it happens for convenience init method but not the designated. See my previous comment

@jasperblues
Copy link
Member

Not good. Since its a convenience initializer that's chaining off the main one can you also try marking that dynamic?

    public dynamic init(temperatureInFahrenheit : NSDecimalNumber) {
        _temperatureInFahrenheit = temperatureInFahrenheit;

        _shortFormatter = NSNumberFormatter()
        _shortFormatter.minimumFractionDigits = 0;
        _shortFormatter.maximumFractionDigits = 0;

        _longFormatter = NSNumberFormatter()
        _longFormatter.minimumFractionDigits = 0
        _longFormatter.maximumFractionDigits = 1

    }

@sahara108
Copy link
Author

It still doesn't work.
Btw, could you please answer my question in SO: http://stackoverflow.com/questions/28084763/how-typhoonscopeobjectgraph-works. Thanks

@jasperblues
Copy link
Member

Will investigate ASAP. Possible to show code on a fork?

Meantime, if this is an urgent problem, suggest using property injection.

sahara108 added a commit to sahara108/Typhoon-Swift-Example that referenced this issue Jan 22, 2015
@sahara108
Copy link
Author

I have uploaded the code. It isn't an urgent problem. I am investigating Typhoon and may use it in the future.

@jasperblues
Copy link
Member

Objective-C is very stable - there are hundreds of Typhoon apps deployed, and we use it daily on our own projects. Typhoon with Swift remains somewhat experimental. Swift, having no native reflection, and aggressively optimizing towards static and vtable dispatch, means there are some quirks.

We are waiting to see if Apple will improve interoperability with Objective-C or instead provide native solutions to reflection and runtime method interception (AOP).

@sahara108
Copy link
Author

Thanks for the information. Since, Swift isn't really stable right now, I intend use both Objc and Swift in my next project, ObjC for the business part and Swift for UI part. Could you please give me some advise about my approach? Is it a good way? Thanks very much.

@jasperblues
Copy link
Member

Could you please give me some advise about my approach? Is it a good way?

I couldn't really say, since it depends on the project, team skills, etc. I'm sure for this specific case you'd know better than me.

As for using Typhoon in a Swift project. It will be more complicated than with Typhoon with Objective-C. We don't currently use Typhoon/Swift ourselves on our own projects yet. Based on the bug reports, for the most part it works, however it is sometimes necessary to make code changes to work around issues. (eg use property injection instead of initializer injection, as suggested above). Its up to you if you think this is acceptable, but we've marked the issue as 'show-stopper'.

DI Options with Swift

  • Use manual dependency injection. Keep pulling dependencies up, until you have a top level 'assembly' responsible for instantiating objects.
  • Use Typhoon. Be aware that there are some quirks, since it uses the Objective-C runtime with Swift. In theory this should be fine, but there appears to be some edge cases where Swift does over-aggressive optimization and workarounds are required.
  • Use a Pure Swift DI container like @jkolb 's Fiery Crucible. Because Swift has no native reflection, the feature set that such a container could provide will be more minimal, however being native there won't be any quirks or edge cases to contend with.

@sahara108
Copy link
Author

I prefer to use dependency injection in my next project and my project also requires to use some C++ libs, so that I will use objC and keep an eye on this framework. Until it is fully compatible with Swift, I will switch the UI code to using Swift. Anyway, thank you for help.

@jasperblues
Copy link
Member

You're welcome. Thanks for choosing Typhoon :)

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

2 participants