3

I'm using Xcode 9.4.1 with Swift 4.1 on iOS 11.4.1.

I've got a handful of protocols, like this:

protocol Bla {
    // some stuff
}

protocol Gorp {
    // some other stuff
}

And I've got some structs that conform to both of those protocols, like this:

typealias MyType = Bla & Gorp

struct Hello: MyType {
    var ID: Int
    var name: String
}

struct Goodbye: MyType {
    var percentage: Double
    var hairbrush: String
}

Then I've got a func that takes an argument, theType, which conforms to both Bla & Gorp. In this example, I'm just printing a description -- like this:

func doSomething<T: MyType>(_ theType: T.Type) {
    // some stuff
    print("Got called... \(theType)")
}

And, I can call this function passing each of the two struct types (Hello and Goodbye), like this:

doSomething(Hello.self)
doSomething(Goodbye.self)

This works great and I get the following output, as expected:

Got called... Hello
Got called... Goodbye

However, what I'd really like to do is to iterate over a bunch of these, rather than calling them individually.

This way gives me an error "note: expected an argument list of type '(T.Type)'":

for thingy in [Hello.self, Goodbye.self] {
    doSomething(thingy)
}

If I add an as! [MyType.Type] or as! [MyType], I get the same error. I've also tried this:

for thingy in [Hello.self as MyType.Type, Goodbye.self as MyType.Type]  {
    doSomething(thingy)
}

Same error as the rest.

I've also tried without the typealias.

If I start typing, the autocomplete says that doSomething is expecting an argument of type (Bla & Gorp).Protocol. So, I also tried this:

for thingy in [Hello.self, Goodbye.self] as! [(Bla & Gorp).Protocol]  {
    doSomething(thingy)
}

In this case, I get the message:

In argument type '(Bla & Gorp).Protocol', 'Bla & Gorp' does not conform to expected type 'Bla'

Also tried this sort of thing, which gave an error, "Cannot invoke 'doSomething' with an argument list of type '(MyType.Type)'":

struct AnyBlaGorp {
    let blaGorp: MyType.Type

    init<T: MyType>(_ theType: T.Type) {
        self.blaGorp = theType
    }
}

for thingy in [AnyBlaGorp(Hello.self), AnyBlaGorp(Goodbye.self)]  {
    doSomething(thingy.blaGorp)
}

Pointers to the magical correct syntax would be greatly appreciated. :)

drewster
  • 5,460
  • 5
  • 40
  • 50
  • Is there any associated types or `Self` in the protocols? – Sweeper Aug 06 '18 at 01:44
  • No there aren't -- I'm using them exactly like in the example given. – drewster Aug 06 '18 at 01:47
  • `for thingy in [Hello.self, Goodbye.self] { doSomething(thingy) }` can't work because Swift is a compiled language. At compile time it needs to know which `doSomething` it is calling on that line of code. – vacawama Aug 06 '18 at 01:50
  • Compare [Why can't I pass a Protocol.Type to a generic T.Type parameter?](https://stackoverflow.com/q/45234233/2976878) – Hamish Aug 06 '18 at 11:19

1 Answers1

4

You can make the doSomething method non-generic and accept a MyType.Type. You can only do this if your protocols don't have Self or associated types.

func doSomething(_ theType: MyType.Type) {
    // some stuff
    print("Got called... \(theType)")
}

Next, you cast the array to a [MyType.Type]:

for thingy in [Hello.self, Goodbye.self] as [MyType.Type] {
    doSomething(thingy)
}

This makes use of the fact that A.self can be converted to the type B.Type if A inherits from/conforms to B.

Sweeper
  • 213,210
  • 22
  • 193
  • 313