通用协议类型参数与直接协议类型的区别

Differences generic protocol type parameter vs direct protocol type

这是我的游乐场代码:

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)

调用带有参数 a 的方法 indirect 时出现编译时错误。所以我理解 <T: A> 表示符合 A 的某种类型。我的变量 a 的类型是 A 并且协议不符合它们自己所以好的,我理解编译时错误。

同样适用于方法direct中的编译时错误。我明白了,需要插入一个具体的符合类型。

尝试访问 direct 中的 static 属性 时也会出现编译时间。

我在想。 定义的2个方法是否有更多差异?我知道我可以从 indirect 调用初始值设定项和静态属性,我可以直接在 direct 中插入类型 A ,我不能做其他人可以做的事。但是我错过了什么吗?

关键的混淆是 Swift 有两个拼写相同的概念,因此常常有歧义。其中一个是struct T: A {},表示"T conforms to the protocol A,",另一个是var a: A,表示"the type of variable a is the existential of A."

遵守协议不会改变类型。 T 仍然是 T。正好符合一些规则。

"existential" 是一个编译器生成的框,它包装了一个协议。这是必要的,因为符合协议的类型可能有不同的大小和不同的内存布局。 existential 是一个盒子,它给任何符合协议的东西在内存中一个一致的布局。存在主义和协议是相关的,但不是一回事。

因为 existential 是一个 运行-time box,可能包含任何类型,所以涉及到一些间接,这会影响性能并阻止某些优化。

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

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

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

f(param: 1)

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

由于为传递的每种类型都创建了通用函数的专门版本,理论上它们可以更优化,因为函数的每个版本将只处理一种类型。权衡是他们可以添加代码膨胀。不要假设 "generics are faster than protocols." 泛型 可能 比协议更快是有原因的,但您必须实际查看代码生成和配置文件才能了解任何特定情况。

因此,通过您的示例:

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

协议 (A) 只是类型必须遵守的一组规则。你不能构造 "some unknown thing that conforms to those rules." 会分配多少字节的内存?它会为规则提供什么实现?

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 的存在包装器。存在包装器不符合协议。它们需要符合协议的东西才能创建它们(这就是它们被称为 "existentials;" 的原因,因为您创建了一个证明这样的值确实存在)。但他们自己并不遵守协议。如果他们这样做了,那么您可以像在 direct() 中尝试做的那样做,然后说 "make a new instance of an existential wrapper without knowing exactly what's inside it." 但没有办法做到这一点。存在的包装器没有自己的方法实现。

在某些情况下,存在的 可以 遵守其自己的协议。只要没有init或者static的要求,其实原则上是没有问题的。但是 Swift 目前无法处理。因为它不适用于 init/static,Swift 目前在所有情况下都禁止它。