PaFi
PaFi

Reputation: 920

Using self in function in a protocol

I have this protocols:

One to instantiate a ViewController from Storyboard:

protocol Storyboarded {
    static func instantiate() -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {

        // this pulls out "MyApp.MyViewController"
        let fullName = NSStringFromClass(self)

        // this splits by the dot and uses everything after, giving "MyViewController"
        let className = fullName.components(separatedBy: ".")[1]

        // load our storyboard
        let storyboard = UIStoryboard(name: "Main", bundle: Bundle.main)

        // instantiate a view controller with that identifier, and force cast as the type that was requested
        return storyboard.instantiateViewController(withIdentifier: className) as! Self
    }
}

One to inject Dependencies in to Viewcontrollers:

protocol DependencyInjection where Self: UIViewController {
    associatedtype myType: DependencyVC
    func injectDependencys(dependency: myType)
}

Now I want to add another one, so I can create the ViewController from the Dependency itself:

protocol DependencyVC {
    associatedtype myType: DependencyInjectionVC & Storyboarded
    func createVC() -> myType
}


extension DependencyVC {
    func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
        let viewController = T.instantiate()
        viewController.injectDependencys(dependency: self)
        return viewController
    }

}

But I get this error for self:

Cannot invoke 'injectDependencys' with an argument list of type '(dependency: Self)'

This is a DependencyClass I have:

class TopFlopDependency: DependencyVC {
    typealias myType = TopFlopVC


    var topFlopState: TopFlopState

    lazy var topFlopConfig: TopFlopConfig = {
        let SIBM = StatIntervalBaseModel(stat: "ppc", interval: "24h", base: "usd")
        return TopFlopConfig(group: Groups.large, base: "usd", valueOne: SIBM)
    }()

    init(state: TopFlopState) {
        self.topFlopState = state
    }

    func createVC() -> TopFlopVC {
        let topflopVC = TopFlopVC.instantiate()
        topflopVC.injectDependencys(dependency: self)

        let viewController: TopFlopVC = makeVC()

        return topflopVC
    }
}

I get this error when using makeVC:

'TopFlopDependency' requires the types 'TopFlopDependency.myType' and 'TopFlopDependency.myType' (aka 'TopFlopVC') be equivalent to use 'makeVC'

other Solution:

protocol DependencyVC {   
}
extension DependencyVC {
    func makeVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self {
        let viewController = T.instantiate()
        viewController.injectDependencys(dependency: self)
        return viewController
    }
}

When trying to use:

let viewController: TopFlopVC = makeVC()

I get the error that T could not be inferred.

Why can I not do this? Do you have a solution how I would get it to work?

Thank you!

Upvotes: 0

Views: 92

Answers (2)

anon
anon

Reputation:

You need to add another constraint. Your DependencyInjection protocol requires a very specific type of DependencyVC (myType). But your DependencyVC extension works with any DependencyVC. So you need to constrain T’s myType to be the same type with a where clause: func createVC<T: Storyboarded & DependencyInjection>() -> T where T.myType == Self

So a complete example would look like this:

protocol Storyboarded {
    static func instantiate() -> Self
}

extension Storyboarded where Self: UIViewController {
    static func instantiate() -> Self {
        ...
    }
}

protocol DependencyVC {
}

protocol DependencyInjection where Self: UIViewController {
    associatedtype myType: DependencyVC
    func injectDependencys(dependency: myType)
}

extension DependencyVC {
    func makeVC<T: Storyboarded & DependencyInjection>(type _: T.Type? = nil) -> T where T.myType == Self {
        let viewController = T.instantiate()
        viewController.injectDependencys(dependency: self)
        return viewController
    }
}

struct MyDependency: DependencyVC {}

class MyVC: UIViewController, Storyboarded, DependencyInjection {
    func injectDependencys(dependency: MyDependency) {
        print(dependency)
    }
}

Upvotes: 1

juliand665
juliand665

Reputation: 3324

When you call viewController.injectDependencys(dependency: self), self is known to be of some subtype of DependencyVC. However, DependencyInjection's associatedtype myType: DependencyVC just says that a type conforming to DependencyInjection will use some type for myType (that conforms to DependencyVC). So there's no guarantee that its actual type will be a subtype of myType.

associatedtypes don't quite work the same way as generic type parameters in that associatedtypes are given when "defining" a type, while generic type parameters are given when "using" a type.

It all boils down to the fact that you probably don't want to have an associatedtype myType, instead taking a DependencyVC directly.

Update

In light of the additional information you've provided, I believe this would be the best solution:

protocol DependencyInjection where Self: UIViewController {
    func injectDependency(_ dependency: DependencyVC)
}

protocol DependencyVC {
    func makeVC<T: Storyboarded & DependencyInjection>() -> T
}

extension DependencyVC {
    func makeVC<T: Storyboarded & DependencyInjection>() -> T {
        let viewController = T.instantiate()
        viewController.injectDependency(self)
        return viewController
    }
}

As you may notice, I took the liberty of renaming injectDependencys(dependency: DependencyVC) to injectDependency(_ dependency: DependencyVC), because you're only injecting one dependency and the dependency: label doesn't really add anything at the call site.

Anyway, this allows you to create instances of view controllers using your dependency. Say you have the dependency stored in a variable named dependency, then you can create a view controller from it by going let topFlopVC: TopFlopVC = dependency.makeVC()

Upvotes: 1

Related Questions