通用协议类型参数与直接协议类型的差异

时间:2019-11-20 20:03:08

标签: swift protocols

这是我的游乐场代码:

protocol A {
    init(someInt: Int)
}

func direct(a: A) {
    // Doesn't work
   let _ = A.init(someInt: 1)
}

func indirect<T: A>(a: T) {
    // Works
    let _ = T.init(someInt: 1)
}

struct B: A {
    init(someInt: Int) {

    }
}

let a: A = B(someInt: 0)

// Works
direct(a: a)

// Doesn't work
indirect(a: a)

调用带有参数indirect的方法a时,会产生编译时错误。因此,我了解<T: A>表示某种符合A的类型。我的变量a的类型为A,协议不符合它们本身,所以我理解编译时错误。

方法direct中的编译时错误也是如此。我了解这一点,需要插入一个具体的符合类型。

尝试访问static中的direct属性时,编译时间也会到来。

我想知道。 所定义的两种方法是否还有更多区别?我知道我可以从indirect调用初始值设定项和静态属性,并且可以分别在A中直接插入类型direct,而我不能做其他人可以做的。但是我错过了什么吗?

1 个答案:

答案 0 :(得分:3)

关键的困惑是,Swift有两个拼写相同的概念,因此常常是模棱两可的。其中一个是struct T: A {},表示“ T符合协议 A”,另一个是var a: A,表示“变量a的类型是A的 existential 。”

符合协议不会更改类型。 T仍为T。它恰好符合某些规则。

“现有”是编译器生成的框,其中包装了协议。这是必要的,因为符合协议的类型可能是不同的大小和不同的内存布局。存在是一个盒子,它为符合协议的所有内容提供了一致的内存布局。存在和协议是相关的,但不是同一件事。

因为存在性是一个可能包含任何类型的运行时框,所以涉及一些间接性,这会带来性能影响并阻止某些优化。

另一个常见的困惑是理解类型参数的含义。在函数定义中:

func f<T>(param: T) { ... }

这定义了一系列函数f<T>(),这些函数是在编译时根据您作为类型参数传递的内容而创建的。例如,当您以这种方式调用此函数时:

f(param: 1)

在编译时会创建一个名为f<Int>()的新函数。这与f<String>()f<[Double]>()完全不同。每个函数都是其自身的功能,原则上是f()中所有代码的完整副本。 (在实践中,优化器非常聪明,可以消除某些复制。而且,与模块边界相关的东西还有其他一些细微之处。但这是考虑发生情况的一种相当不错的方式。)

由于通用函数的专用版本是为传递的每种类型创建的,因此从理论上讲,它们可以得到更好的优化,因为该函数的每种版本将只处理一种类型。折衷是他们可以添加代码膨胀。不要假设“泛型比协议快”。出于某种原因,泛型 可能比协议要快,但是您必须实际查看代码的生成和配置文件以了解在任何特定情况下。

因此,请遍历您的示例:

func direct(a: A) {
    // Doesn't work
   let _ = A.init(someInt: 1)
}

协议(A)只是类型必须遵守的一组规则。您不能构造“符合这些规则的未知事物”。将分配多少字节的内存?它会为规则提供哪些实现?

func indirect<T: A>(a: T) {
    // Works
    let _ = T.init(someInt: 1)
}

要调用此函数,必须传递类型参数T,并且该类型必须符合A。以特定类型调用它时,编译器将创建一个indirect的新副本,该副本专为通过T而设计。由于我们知道T具有适当的init,因此我们知道编译器可以在需要时编写此代码。但是indirect只是用于编写函数的模式。它本身不是一个函数。直到您给它一个T一起使用。

let a: A = B(someInt: 0)

// Works
direct(a: a)

a是围绕B的存在性包装器。direct()需要存在性的包装器,因此您可以通过它。

// Doesn't work
indirect(a: a)

a是围绕B的存在性包装器。存在性包装器不符合协议。它们需要符合协议的内容才能创建它们(这就是为什么它们被称为“存在的”的原因;您创建的事实证明了这样的值确实存在)。但是它们本身并不符合协议。如果他们这样做了,那么您可以像在direct()中尝试做的事情那样做,并说“创建一个存在性包装器的新实例,而无需确切知道其中的内容”。而且没有办法做到这一点。现有的包装器没有自己的方法实现。

在某些情况下,存在性可以符合其自己的协议。只要没有initstatic的要求,原则上实际上就没有问题。但是Swift目前无法处理。由于它不能用于init / static,因此Swift目前在所有情况下均禁止使用它。

相关问题