为什么我不能将Protocol.Type传递给通用T.Type参数?

时间:2017-07-21 09:36:40

标签: swift generics protocols

我正在与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),但它们都没有工作。

所以我想我不能使用作为变量提供的泛型参数来调用函数?

2 个答案:

答案 0 :(得分:27)

P.TypeP.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怎么办?如果TT.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一次。