本文介绍了为什么我不能将 Protocol.Type 传递给通用 T.Type 参数?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用 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,因为Anytop type,所以任何元类型值都可以输入为Any.Type;包括Any.self).
  • A P.Protocol describes the type of a protocol itself (the only value it can hold is P.self).
  • A 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).

您面临的问题是,对于给定的通用占位符 T,当 T 是某种协议 PT.Typenot 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() on P has an implicit generic placeholder Self that's constrained to P. The implicit self 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.TypeP.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 参数?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

08-14 01:43