0

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!

PaFi
  • 888
  • 1
  • 9
  • 24
  • 1
    You're essentially running into the problem of protocols not conforming to themselves. See: [Protocol doesn't conform to itself](https://stackoverflow.com/questions/33112559/protocol-doesnt-conform-to-itself). In `createVC`, all the compiler knows about `self` is that it is `DependencyVC`, which doesn't conform to itself. – Dávid Pásztor Nov 22 '18 at 14:31

2 Answers2

1

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()

juliand665
  • 2,664
  • 1
  • 16
  • 16
  • Hey Julian thanks for your answer. How would this solution look like? I updated my question and added two solutions, but could still not figure it out – PaFi Nov 23 '18 at 11:24
  • @PaFi I've updated my answer with concrete code that hopefully reflects the new information you've given and works for you. – juliand665 Nov 23 '18 at 15:37
  • Thanks for you answer! I tried it but this leads to the same problem as before. But Svens solution works. Thanks anyways! – PaFi Nov 26 '18 at 09:29
  • @PaFi Ah, I think I get it. You want to inject only specific types of dependencies into specific view controllers. In that case, the `associatedtype` is desirable and Sven’s answer is indeed the way to go. – juliand665 Nov 26 '18 at 11:32
1

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)
    }
}
Sven
  • 22,475
  • 4
  • 52
  • 71
  • Hey Sven, thanks for your answer I tried your solution. But I could not get it to work. I updated my question maybe you can help me figure it out. This is the error I get: 'TopFlopDependency' requires the types 'TopFlopDependency.myType' and 'TopFlopDependency.myType' (aka 'TopFlopVC') be equivalent to use 'makeVC' – PaFi Nov 23 '18 at 11:13
  • I added a complete example to my answer. – Sven Nov 23 '18 at 17:04
  • One small addition to your signature for `makeVC`: you can just make the `type` argument non-optional and default it to `T.self`. Looks a bit nicer imo. – juliand665 Nov 26 '18 at 11:30
  • @juliand665 that is nicer indeed. Of course the type parameter is not strictly necessary, it also works without. – Sven Nov 26 '18 at 18:04