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

Gui*_* L. 17 generics protocols swift

我正在使用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)'"
Run Code Online (Sandbox Code Playgroud)

我尝试了不同的解决方案,如test.self或type(of:test),但它们都不起作用.

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

Ham*_*ish 29

P.TypeP.Protocol

协议元类型有两种.对于某些协议P和符合类型C:

  • A P.Protocol描述了协议本身的类型(它可以容纳的唯一值P.self).
  • A P.Type描述了符合协议的具体类型.它可以容纳的值C.self,但不会 P.self因为协议不符合自己(虽然一个例外是Any,因为Any顶级型,因此任何元类型的值可以分型为Any.Type,其中包括Any.self).

你面临的问题是,对于一个给定的通用占位符T,当T一些协议P,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)
Run Code Online (Sandbox Code Playgroud)

我们不能test作为论据来传递printType(serviceType:).为什么?因为testP.Type; 并且没有替代,T这使得serviceType:参数占用a P.Type.

如果我们替换PT,参数采用P.Protocol:

printType(serviceType: P.self) // fine, P.self is of type P.Protocol, not P.Type
Run Code Online (Sandbox Code Playgroud)

如果我们在具体类型中替换T,例如C,参数需要C.Type:

printType(serviceType: C.self) // C.self is of type C.Type
Run Code Online (Sandbox Code Playgroud)

黑客攻击协议扩展

好的,所以我们已经知道如果我们可以用具体类型代替T,我们可以传递C.Type给函数.我们可以替换P.Type包装的动态类型吗?不幸的是,这需要一种称为开放存在的语言功能,该功能目前不能直接供用户使用.

但是,当访问协议类型实例或元类型上的成员时,Swift 隐式打开存在性(即它挖掘运行时类型并使其以通用占位符的形式访问).我们可以在协议扩展中利用这个事实:

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
Run Code Online (Sandbox Code Playgroud)

这里有很多东西,所以让我们解压一下:

  • 扩展成员callPrintType()on P具有Self受约束的隐式通用占位符P.self使用此占位符键入隐式参数.

  • 当调用callPrintType()a时P.Type,Swift隐式挖掘出P.Type包装的动态类型(这是存在主义的开头),并使用它来满足Self占位符.然后它将此动态元类型传递给隐式self参数.

  • 因此,Self将被满足C,然后可以转发到printType通用占位符T.


为什么T.Type不是P.Type时候T == P

您将注意到上述解决方法的工作原理,因为我们避免使用Pfor 替换通用占位符T.但是,为什么在协议类型时替换PT,是T.Type 不是 P.Type

好吧,考虑一下:

func foo<T>(_: T.Type) {
    let t: T.Type = T.self
    print(t)
}
Run Code Online (Sandbox Code Playgroud)

如果我们替换PT怎么办?如果T.TypeP.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)
}
Run Code Online (Sandbox Code Playgroud)

这是非法的; 我们不能分配P.selfP.Type,因为它的类型P.Protocol,不是P.Type.

因此,结果是如果你想要一个函数参数,它采用一个描述符合任何具体类型的元类型P(而不仅仅是一个特定的具体符合类型) - 你只需要一个P.Type参数,而不是泛型.泛型不会对异构类型进行建模,这就是协议类型的用途.

而这正是你所拥有的printType(conformingClassType:):

func printType(conformingClassType: P.Type) {
    print(conformingClassType)
}

printType(conformingClassType: test) // okay
Run Code Online (Sandbox Code Playgroud)

你可以传递test给它,因为它有一个类型的参数P.Type.但是你会注意到现在这意味着我们无法传递P.self给它,因为它不是类型P.Type:

// Cannot convert value of type 'P.Protocol' to expected argument type 'P.Type'
printType(conformingClassType: P.self) 
Run Code Online (Sandbox Code Playgroud)

  • 感谢您提供非常完整的答案,至少可以说这不是微不足道的。所以我想我不能做我想做的事,这有点可悲。您是否认为有解决方法或应该以不同方式完成的事情才能使之成为可能? (2认同)