我正在与Swinject合作,一个问题是困扰我。我差不多整整一天都被困在这里。我怀疑这是因为Swift是一种属于某种形式的语言,但我并不完全确定。
我在这个操场上总结了我的问题
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),但它们都没有工作。
所以我想我不能使用作为变量提供的泛型参数来调用函数?
答案 0 :(得分:27)
P.Type
与P.Protocol
有两种协议元类型。对于某些协议P
和符合类型C
:
P.Protocol
描述协议本身的类型(它可以容纳的唯一值是P.self
)。P.Type
描述符合协议的具体类型。它可以包含值C.self
,但不 P.self
,因为protocols don't conform to themselves(虽然此规则的一个例外是Any
,因为{{1} {}是top type,因此任何元类型值都可以输入Any
;包括Any.Type
)。您遇到的问题是,对于给定的通用占位符Any.self
,当T
是某个协议T
时,P
不< / em> T.Type
- 它是P.Type
。
所以如果我们回到你的例子:
P.Protocol
我们无法将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
中替换P
,则参数需要T
:
P.Protocol
如果我们在printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
的具体类型中替换T
,则参数需要C
:
C.Type
好的,我们已经了解到,如果我们可以用具体的类型代替printType(serviceType: C.self) // C.self is of type C.Type
,我们可以将T
传递给该函数。我们可以替换C.Type
包裹的动态类型吗?不幸的是,这需要一种名为opening existentials的语言功能,目前该功能无法直接供用户使用。
但是,当访问协议类型实例或元类型上的成员时,Swift 隐式打开存在性(即它挖掘出运行时类型并使其以通用占位符的形式访问)。我们可以在协议扩展中利用这一事实:
P.Type
这里有很多东西,所以让我们解开一下:
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
上的扩展成员callPrintType()
有一个隐含的通用占位符P
,其约束为Self
。使用此占位符键入隐式P
参数。
在self
上调用callPrintType()
时,Swift会隐式挖掘出P.Type
包裹的动态类型(这是存在主义的开头),并将其用于满足P.Type
占位符。然后,它将此动态元类型传递给隐式Self
参数。
因此,self
会满足Self
,然后可以将其转发到C
的通用占位符printType
。
T
时为什么T.Type
不是P.Type
?您会注意到上述解决方法的工作原理,因为我们避免在T == P
中替换通用占位符P
。但是,为什么在T
的协议类型P
中替换时,T
不是 T.Type
?
好吧,考虑一下:
P.Type
如果我们用func foo<T>(_: T.Type) {
let t: T.Type = T.self
print(t)
}
代替P
怎么办?如果T
是T.Type
,那么我们得到的是:
P.Type
这是非法的;我们无法将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
的具体类型(而不仅仅是一个特定的具体符合类型) - 你只想要一个P
参数,而不是泛型。泛型不会模拟异类,这是什么协议类型。
这正是你对P.Type
所拥有的:
printType(conformingClassType:)
您可以将func printType(conformingClassType: P.Type) {
print(conformingClassType)
}
printType(conformingClassType: test) // okay
传递给它,因为它的参数类型为test
。但是,您现在注意到这意味着我们无法将P.Type
传递给它,因为它不属于P.self
类型:
P.Type
答案 1 :(得分:1)
我在操场上运行了你的代码,似乎这就是为什么它不会编译
let test: Protocol.Type = Class.self
如果您删除了test
的类型声明,代码就会生效,并会在Class.Type
行打印15
。
所以下面的代码编译并运行:
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"
我希望这有助于解决您的问题。
修改强>
我在操场上使用原始代码获得的错误如下:
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"
这意味着您正在调用Type
2次,这就是代码无法编译的原因,因为您已经使用Protocol.Type
类型的参数调用了该方法。
如果您更改方法的签名如下:
让test:Protocol.Type = Class.self
func printType<Service>(serviceType: Service) {
print(serviceType)
}
一切都将编译并正常工作,打印Class.type
这也是我的第一个答案版本会编译的原因,因为它会为test
分配正确的类型,只能调用.Type
一次。