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 270 forks source link

Resolve-by-type for ViewController using storyboard #589

Closed AdithyaReddy closed 6 years ago

AdithyaReddy commented 6 years ago

` // Storyboard definition @objc open dynamic func homeStoryBoard() -> Any { return TyphoonDefinition.withClass(TyphoonStoryboard.self) { (definition) -> Void in definition!.useInitializer(#selector(TyphoonStoryboard.init(name:factory:bundle:))){ (initializer) -> Void in initializer?.injectParameter(with: "Home") initializer?.injectParameter(with: self) initializer?.injectParameter(with: Bundle.main) }} }


// ViewController init through storyboard @objc open dynamic func categoryListViewController() -> Any { return TyphoonStoryboardDefinition.withStoryboard(self.homeStoryBoard(), viewControllerId: "categoryListViewController", configuration: { (definition) in definition?.injectProperty(NSSelectorFromString("assembly"), with:self) }) }


//Resolving the ViewController by type private func getClassByType(with factory: TyphoonComponentFactory, type: T.Type) throws -> T { return factory.component(forType: T.self) as! T } `

The problem is when I try to resolve CategoryListViewController by type(which is initialised through storyboard) it crashes. And the exception I get is :

No components defined which satisify type: categoryListViewController

This issue/crash doesn't happen when I resolve through key. It neither poses problem in non-storyboard init'ed viewcontrollers.

Could you please help me resolve-by-type(not by key) for ViewController(using storyboard)? @jasperblues @alexgarbarev

Thanks in advance.

alexgarbarev commented 6 years ago

The problem happens because use using TyphoonStoryboardDefinition.withStoryboard which doesn't know about viewController type, returned by storyboard. You should specify it explicit. For that, go into configuration block of categoryListViewController definition and set autoInjectionVisibility property and use method setClassOrProtocolForAutoInjection. Let me know if it clear and works for you.

AdithyaReddy commented 6 years ago

Wow, thanks for the lightning fast response. And yeah, it very well worked :) Thanks a lot.

AdithyaReddy commented 6 years ago

One last thing to clarify:

`@objc open dynamic func mainNavigationController() -> Any { return TyphoonDefinition.withClass(UCNavigationController.self) { (definition) in

        definition?.scope = TyphoonScope.prototype
        definition?.injectProperty(NSSelectorFromString("analyticsClient"), with:self.coreAssembly?.analyticsLogger())
    }
}

@objc open dynamic func rootNavigationController() -> Any {
    return TyphoonDefinition.withClass(**RootNavigationController**.self) {
        (definition) in
        definition?.useInitializer(#selector(**RootNavigationController**.init(rootViewController:)), parameters: { (method) in
            method?.injectParameter(with: self.mainTabBarController())
        })
        definition?.injectProperty(NSSelectorFromString("navigationBarHidden"), with: true)

        definition?.scope = TyphoonScope.prototype
        }
}`

Basically RootNavigationController is a subclass of UCNavigationController. RootNavigationController was created just to make typhoon init two definitions of same object type.

The problem here is, I just tried adding 'autoInjectionVisibility' to the above mainNavigationController()'s definition block. But when I tried to access UCNavigationController by type and typhoon threw an error stating that

More than one component is defined satisfying type: 'UCNavigationController' :2 candidates

@alexgarbarev 1) any idea(or a workaround, other than subclassing) how to create 2 definitions for same object type? and 2) Even if I have two definitions(by subclassing), when I try to access them by type, typhoon throws above mentioned error, any workaround for this?

Thanks

alexgarbarev commented 6 years ago

That's because access by type includes subclasses in search by default. I.e. you can try to find a UIViewController and get much more candidates in exception. You should access by key, if your type is ambiguous for Typhoon, or make a new specific subclass for your case and then access by that type.

AdithyaReddy commented 6 years ago

Makes sense. Will try it. Thank you :)

alexgarbarev commented 6 years ago

any idea(or a workaround, other than subclassing) how to create 2 definitions for same object type? and

Even if I have two definitions(by subclassing), when I try to access them by type, typhoon throws above mentioned error, any workaround for this?

-You can access subclass by type, but base class should be accessed by key.

AdithyaReddy commented 6 years ago

Yeah. Agreed. I went with subclass-each-type-and-access-by-type approach rather than resolve-base-class-by-key approach. Thanks for the clarification.

AdithyaReddy commented 6 years ago
func getClass<T: AnyObject>(props: [String: Any], type: T.Type) -> T? {
        return TyphoonDefinition.withClass(T.self, configuration: {
            (definition) -> Void in
            props.forEach({ (key, selector) in
                definition?.injectProperty(NSSelectorFromString(key), with: selector)
            })
            definition?.autoInjectionVisibility = TyphoonAutoInjectVisibility.byClass
            if let definition = definition as? TyphoonFactoryDefinition {
                definition.classOrProtocolForAutoInjection = T.self
            }
        }) as? T
    }

@objc dynamic public func searchCategoryViewController() -> SearchCategoryViewController? {

        var properties: [String: Any] = [String: Any]()
        properties["homeAssembly"] = self
        properties["analyticsClient"] = self.coreAssembly?.analyticsLogger()
        properties["searchResultsView"] = self.uiAssembly?.homeSearchView()
        return getClass(props: properties, type: SearchCategoryViewController.self)
    }

@alexgarbarev I was actually trying to extract out the Typhoon definition part out of the definition into a common function using Swift Generics.

But then I get a crash when the line 'return getClass()' gets executed.

Thread 1: EXC_BAD_ACCESS (code=2, address=0x119c15e58)

Any idea if the problem is with Generics not being properly supported by Objc or is there limitation in Typhoon with regards to the same that am unaware of?

Could you help me with a solution/workaround to this?

@jasperblues @alexgarbarev Thanks