appsquickly / typhoon

Powerful dependency injection for Objective-C ✨✨ (https://PILGRIM.PH is the pure Swift successor to Typhoon!!)✨✨
https://pilgrim.ph
Apache License 2.0
2.7k stars 269 forks source link

Instantiate Assembly Define in swift via Plist Integration Inside Objective-c project #559

Closed msalmanST closed 7 years ago

msalmanST commented 7 years ago

Hi,

So I have a legacy application which is written in objective-c, we are now adopting swift and currently we have a mix of objc and swift code.

I want to integrate the typhoon lib so I have added it as a pod. After that I create one test assembly in swift file like below. ( I try it with and without @objc directive)

@objc public class ApplicationAssembly: TyphoonAssembly {}

I have also added the entry in the plist file as required by the instantiation of assemblies. Now at runtime I am getting the below error.

*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Can't resolve assembly for name ApplicationAssembly.

Please help me.

vasilenkoigor commented 7 years ago

Hello, @msalmanST Please, can you show example of your plist file?

msalmanST commented 7 years ago

This is my entry of assembly in plist. screen shot 2017-05-16 at 7 14 55 pm

ApplicationAssemblyObjc gets loaded by the code successfully but ApplicationAssembly is creating a runtime exception at file "TyphoonAssemblyBuilder+PlistProcessor.m" line number 28

vasilenkoigor commented 7 years ago

@msalmanST Ok, all clear. Please, can you read note for TyphoonClassFromString function? Could this be useful for you?

Class TyphoonClassFromString(NSString *className)
{
    if (TyphoonIsInvalidClassName(className)) {
        return Nil;
    }

    Class clazz = NSClassFromString(className);
    if (!clazz) {
        clazz = NSClassFromString([TyphoonDefaultModuleName() stringByAppendingFormat:@".%@", className]);
    }
    if (!clazz) {
        /**
        Swift issue:
        When calling property_getAttributes() for a Swift class that belongs in a different
        module, the module name is *NOT* returned, therefore attempting to resolve the
        Swift class via name wont work:
        1. Foo.BarAssembly in Foo.framework
        2. App.app includes Foo.framework and references Foo.BarAssembly in AppAssembly
        import Foo
        class AppAssembly: TyphonAssembly {
        var barAssembly: Foo.BarAssembly?
        }
        3. property_getAttributes() called on `barAssembly` returns @T"BarAssembly" but
        should return @T"Foo.BarAssembly"
        4. NSClassforString("BarAssembly") returns nil because the App.app does not define
        this class
        Poor workaround, not the best, but no other way around it unless Apple fixes the objc reflection of
        Swift classes there is no other way:
        If we are unable to find the class in the top level project iterate all dynamic
        frameworks and attempt to resolve the class name by appending the module name
        This is somewhat brittle as we loose the namespacing modules give us (for example,
        if 2 modules define the same class then which ever appears first in the list will
        be used which might not be expected). In order for this to work, developers are required
        to:
        - have unique names of assemblies across modules
        - frameworks bundle ids much match the following format: *.{Name} where Name is
        the name of the module
        */

        //Those class names will never be resolved, so no reason to try
        if(!className || [className isEqual:@"?"] || [className isEqual:@""]) {
            return nil;
        }

        clazz = TyphoonClassFromFrameworkString(className);

    }
    return clazz;
}
msalmanST commented 7 years ago

Ohhh, so I have to use my module name with the swift class in oder to access it. It works man (Y) thanks for pointing that comment, I missed that :).

Another quick question, I saw that on app launch all the methods written in the assembly gets called on app launch this means all the objects define in the assembly will get created with their dependencies at the app launch time, how I can make them lazy load, so that every object gets instantiate when any part of the app ask for that object and not before.

jasperblues commented 7 years ago

Hi @msalmanST

All methods on the assembly called called on startup, just so that Typhoon has a blue-print for each defined object. Then, only singletons are pre-instantiated. Typhoon, along with its assembly rules is then retained as a factory for emitting objects.