P.Type vs. P.Protocol
There are two types of protocol metatypes. For some protocol P and corresponding type C :
- A
P.Protocol describes the type of protocol itself (the only value it can hold is P.self ). - A
P.Type describes a specific protocol type. It may contain the value C.self , but not P.self because the protocols do not match themselves (although One exception to this rule is Any , as Any is a top type , so any metatype value can be printed as Any.Type , including Any.self ).
The problem you are facing is that for a given generic placeholder T , when T is the protocol of P , T.Type not P.Type is P.Protocol .
So, if we go back to your example:
protocol P {} class C : P {} func printType<T>(serviceType: T.Type) { print(serviceType) } let test: P.Type = C.self // Cannot invoke 'printType' with an argument list of type '(serviceType: P.Type)' printType(serviceType: test)
We cannot pass test as an argument to printType(serviceType:) . What for? Because test is P.Type ; and there is no substitute for T , which makes the serviceType: parameter serviceType: accept P.Type .
If we replace in P for T , the parameter will take P.Protocol :
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute T specific type, for example C , the parameter accepts C.Type :
printType(serviceType: C.self) // C.self is of type C.Type
Hacking with protocol extension
Okay, so we learned that if we can substitute a specific type for T , we can pass C.Type function. Can we replace the dynamic type that wraps P.Type ? Unfortunately, this requires the discovery of the existential function of the language, which is currently not available to users.
However, Swift implicitly opens up the existences when accessing members in an instance or metatepe based on the protocol (i.e. it digs out the type of runtime and makes it available as a generic placeholder). We can use this fact in the extension of the protocol:
protocol P {} class C : P {} func printType<T>(serviceType: T.Type) { print("T.self = \(T.self)") print("serviceType = \(serviceType)") } extension P { static func callPrintType() { printType(serviceType: self) } } let test: P.Type = C.self test.callPrintType()
There are quite a few things here, so unzip it a bit:
The extension element callPrintType() on P has an implicit shared placeholder Self , which is limited to P The implicit parameter Self is entered using this placeholder.
When calling callPrintType() on P.Type Swift implicitly digs out the dynamic type that wraps P.Type (this is an existential discovery) and uses it to match the Self placeholder.Then it passes this dynamic metatype to the implicit Self parameter.
So, Self will execute C , which can then be redirected to printType common T placeholder.
Why is T.Type not P.Type when T == P ?
You will notice how the above workaround works because we avoided replacing P with a common placeholder T But why, when substituting in the protocol type P for T is T T.Type not P.Type ?
Ok, consider:
func foo<T>(_: T.Type) { let t: T.Type = T.self print(t) }
What if we replaced in P by T ? If T.Type is P.Type , then we have:
func foo(_: P.Type) { // Cannot convert value of type 'P.Protocol' to specified type 'P.Type' let p: P.Type = P.self print(p) }
what is illegal; we cannot assign P.self to P.Type , as it is a type of P.Protocol , not P.Type .
So, the result is that if you want the function parameter to accept a metatype that describes a specific type that matches P (and not just the specific concrete type) - you just need the P.Type parameter, not the generics. Generics do not model heterogeneous types, for what types of protocols.
And this is exactly what you have with printType(conformingClassType:) :
func printType(conformingClassType: P.Type) { print(conformingClassType) } printType(conformingClassType: test) // okay
You can pass test to it because it has a parameter of type P.Type . But you will notice that this means that we cannot pass P.self to it, since it is not a P.Type type:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type' printType(conformingClassType: P.self)