I was working with Swinject and a problem is bugging me. I have been stuck one this for almost an entire day. I suspect this is due to Swift being a statictly typed language but I'm not entirely sure.
I summarized my problem in this playground
protocol Protocol {}
class Class: Protocol {}
let test: Protocol.Type = Class.self
func printType(confromingClassType: Protocol.Type) {
print(confromingClassType)
}
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.self) // "Class"
printType(serviceType: Class.self) // "Class"
print(test) // "Class"
printType(confromingClassType: test) // "Class"
printType(serviceType: test) // "note: expected an argument list of type '(serviceType: Service.Type)'"
I tried different solutions like test.self or type(of: test) but none of them work.
So I guess I can't call a function with a generic parameter provided as a variable ?
Making a Protocol Generic. There are two ways to create a generic protocol - either by defining an abstract associatedtype or the use of Self (with a capital S). The use of Self or associatedtype is what we like to call "associated types". This is because they are only associated with the protocol they are defined in.
Swift 4 language provides 'Generic' features to write flexible and reusable functions and types. Generics are used to avoid duplication and to provide abstraction. Swift 4 standard libraries are built with generics code. Swift 4s 'Arrays' and 'Dictionary' types belong to generic collections.
P.Type
vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P
, and a conforming type C
:
P.Protocol
describes the type of a protocol itself (the only value it can hold is P.self
).P.Type
describes a concrete type that conforms to the protocol. It can hold a value of C.self
, but not P.self
because protocols don't conform to themselves (although one exception to this rule is Any
, as Any
is the top type, so any metatype value can be typed as Any.Type
; including Any.self
).The problem you're facing is that for a given generic placeholder T
, when T
is some protocol P
, T.Type
is not P.Type
– it is P.Protocol
.
So if we jump 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:)
. Why? Because test
is a P.Type
; and there's no substitution for T
that makes the serviceType:
parameter take a P.Type
.
If we substitute in P
for T
, the parameter takes a P.Protocol
:
printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
If we substitute in a concrete type for T
, such as C
, the parameter takes a C.Type
:
printType(serviceType: C.self) // C.self is of type C.Type
Okay, so we've learnt that if we can substitute in a concrete type for T
, we can pass a C.Type
to the function. Can we substitute in the dynamic type that the P.Type
wraps? Unfortunately, this requires a language feature called opening existentials, which currently isn't directly available to users.
However, Swift does implicitly open existentials when accessing members on a protocol-typed instance or metatype (i.e it digs out the runtime type and makes it accessible in the form of a generic placeholder). We can take advantage of this fact in a protocol extension:
protocol P {}
class C : P {}
func printType<T>(serviceType: T.Type) {
print("T.self = \(T.self)")
print("serviceType = \(serviceType)")
}
extension P {
static func callPrintType/*<Self : P>*/(/*_ self: Self.Type*/) {
printType(serviceType: self)
}
}
let test: P.Type = C.self
test.callPrintType()
// T.self = C
// serviceType = C
There's quite a bit of stuff going on here, so let's unpack it a little bit:
The extension member callPrintType()
on P
has an implicit generic placeholder Self
that's constrained to P
. The implicit self
parameter is typed using this placeholder.
When calling callPrintType()
on a P.Type
, Swift implicitly digs out the dynamic type that the P.Type
wraps (this is the opening of the existential), and uses it to satisfy the Self
placeholder. It then passes this dynamic metatype to the implicit self
parameter.
So, Self
will be satisfied by C
, which can then be forwarded onto printType
's generic placeholder T
.
T.Type
not P.Type
when T == P
?You'll notice how the above workaround works because we avoided substituting in P
for the generic placeholder T
. But why when substituting in a protocol type P
for T
, is T.Type
not P.Type
?
Well, consider:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
What if we substituted in P
for T
? If T.Type
is P.Type
, then what we've got is:
func foo(_: P.Type) {
// Cannot convert value of type 'P.Protocol' to specified type 'P.Type'
let p: P.Type = P.self
print(p)
}
which is illegal; we cannot assign P.self
to P.Type
, as it's of type P.Protocol
, not P.Type
.
So, the upshot is that if you want a function parameter that takes a metatype describing any concrete type that conforms to P
(rather than just one specific concrete conforming type) – you just want a P.Type
parameter, not generics. Generics don't model heterogenous types, that's what protocol types are for.
And that's 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'll note this now means we cannot pass P.self
to it, as it is not of type P.Type
:
// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self)
I've ran your code in a playground, and it seems that this is the reason why it wont compile
let test: Protocol.Type = Class.self
If you remove the type declaration for test
, the code will work and will print out Class.Type
at line 15
.
So the following code compiles and runs:
protocol Protocol {}
class Class: Protocol {}
let test = Class.self
func printType<Service>(serviceType: Service.Type) {
print(serviceType)
}
print(Class.Type.self) // "Class.Type"
printType(serviceType: Class.Type.self) // "Class.Type"
print(type(of: test)) // "Class.Type"
printType(serviceType: type(of: test)) // "Class.Type"
I hope this helps with your problem.
Edit
The error I am getting in the playground with the original code is the following:
Playground execution failed: error: Untitled Page 2.xcplaygroundpage:9:1: error: cannot invoke 'printType' with an argument list of type '(serviceType: Protocol.Type.Type)'
printType(serviceType: type(of: test)) // "Class.Type"
This means you are calling Type
2 times, that's why the code will not compile, because the type you are already calling the method with the argument of type Protocol.Type
.
If you change the method's signature like this:
let test: Protocol.Type = Class.self
func printType<Service>(serviceType: Service) {
print(serviceType)
}
everything will compile and work right, printing Class.type
This is also the reason my first version of the answer will compile, since it will assign the right type for test
can call .Type
only once.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With