首页 > 解决方案 > 通用协议类型参数与直接协议类型的区别

问题描述

这是我的游乐场代码:

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. 我的变量的类型aA和协议不符合他们自己所以好的,我理解编译时错误。

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

尝试访问static.direct

我想知道。定义的两种方法是否有更多差异?我知道我可以调用初始化程序和静态属性,并且可以分别直接indirect插入类型,我不能做其他人可以做的事情。但是有什么我错过的吗?Adirect

标签: swiftprotocols

解决方案


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

遵守协议不会改变类型。T还在T。它只是碰巧符合一些规则。

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

因为existential 是一个可以容纳任何类型的运行时框,所以涉及到一些间接性,这可能会影响性能并阻止某些优化。

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

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 的存在包装器。存在包装器不符合协议。它们需要符合协议的东西才能创建它们(这就是为什么它们被称为“existentials”;您创建了一个事实证明这样的值确实存在)。但它们本身并不符合协议。如果他们这样做了,那么您可以执行您尝试过的操作direct()并说“创建一个存在包装器的新实例,而无需确切知道其中的内容。” 没有办法做到这一点。存在包装器没有自己的方法实现。

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


推荐阅读