问题描述
我正在使用 Swinject,但有一个问题困扰着我.我几乎一整天都被困在这个问题上.我怀疑这是因为 Swift 是一种静态类型语言,但我并不完全确定.
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)'"
我尝试了不同的解决方案,例如 test.self 或 type(of: test) 但它们都不起作用.
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 ?
推荐答案
P.Type
vs. P.Protocol
有两种协议元类型.对于某些协议P
,以及符合类型C
:
P.Type
vs. P.Protocol
There are two kinds of protocol metatypes. For some protocol P
, and a conforming type C
:
- A
P.Protocol
描述了协议本身的类型(它可以保存的唯一值是P.self
). - A
P.Type
描述了符合协议的具体类型.它可以保存C.self
的值,但 不能P.self
因为 协议不符合自身(尽管此规则的一个例外是Any
,因为Any
是top type,所以任何元类型值都可以输入为Any.Type
;包括Any.self
).
- A
P.Protocol
describes the type of a protocol itself (the only value it can hold isP.self
). - A
P.Type
describes a concrete type that conforms to the protocol. It can hold a value ofC.self
, but notP.self
because protocols don't conform to themselves (although one exception to this rule isAny
, asAny
is the top type, so any metatype value can be typed asAny.Type
; includingAny.self
).
您面临的问题是,对于给定的通用占位符 T
,当 T
是某种协议 P
,T.Type
是 not P.Type
– 它是 P.Protocol
.
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)
我们不能将 test
作为参数传递给 printType(serviceType:)
.为什么?因为test
是一个P.Type
;并且没有替换 T
使 serviceType:
参数采用 P.Type
.
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
.
如果我们用 P
替换 T
,则参数采用 P.Protocol
:
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
如果我们用具体类型替换T
,例如C
,则参数采用C.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
使用协议扩展进行黑客攻击
好的,所以我们已经了解到,如果我们可以用一个 具体 类型替换 T
,我们可以传递一个 C.Type
到函数.我们可以在 P.Type
包装的动态类型中替换吗?不幸的是,这需要一个名为 openingexistentials,目前用户无法直接使用.
Hacking around with protocol extensions
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.
然而,Swift 确实在访问协议类型实例或元类型上的成员时隐式地打开存在项(即它挖掘出运行时类型并使其以通用占位符的形式访问).我们可以在协议扩展中利用这一事实:
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:
P
上的扩展成员callPrintType()
有一个隐含的通用占位符Self
,它被限制为P代码>.使用此占位符键入隐式
self
参数.
The extension member
callPrintType()
onP
has an implicit generic placeholderSelf
that's constrained toP
. The implicitself
parameter is typed using this placeholder.
当在 P.Type
上调用 callPrintType()
时,Swift 会隐式地挖掘出 P.Type
的动态类型包装(这是存在的开始),并使用它来满足 Self
占位符.然后它将这个动态元类型传递给隐式 self
参数.
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.
所以,Self
将被 C
满足,然后可以转发到 printType
的通用占位符 T
.
So, Self
will be satisfied by C
, which can then be forwarded onto printType
's generic placeholder T
.
您会注意到上述解决方法的工作原理,因为我们避免用 P
替换通用占位符 T
.但是为什么当在协议类型 P
替换 T
时,是 T.Type
not 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
?
好吧,考虑:
func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
如果我们用 P
代替 T
会怎样?如果 T.Type
是 P.Type
,那么我们得到的是:
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)
}
这是非法的;我们不能将 P.self
分配给 P.Type
,因为它属于 P.Protocol
类型,而不是 P.Type
>.
which is illegal; we cannot assign P.self
to P.Type
, as it's of type P.Protocol
, not P.Type
.
因此,结果是,如果您想要一个函数参数,它采用描述任何符合P
的具体类型的元类型(而不仅仅是一个特定的具体符合类型)) – 你只需要一个 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.
这正是您使用 printType(conformingClassType:)
:
func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
您可以将 test
传递给它,因为它有一个 P.Type
类型的参数.但是你现在会注意到这意味着我们不能将 P.self
传递给它,因为它不是 P.Type
类型:
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)
这篇关于为什么我不能将 Protocol.Type 传递给通用 T.Type 参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!