swift - 通用协议类型参数与直接协议类型的区别
问题描述
这是我的游乐场代码:
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
解决方案
关键的混淆是 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 目前在所有情况下都禁止它。
推荐阅读
- sql - 具有继承的 PostgreSQL 外键
- r - 我如何计算变量不能相互分离的 2 列中的观察次数
- wcf - Xamarin.Forms 在 WCF 上添加连接服务仅生成异步方法
- azure - 如何在 Azure 中使用 Terraform 创建多个安全规则?
- amazon-s3 - 如何在 S3 上上传字体真棒图标
- java - 如何消除 POI XSSFWorkbook 中的零值数据栏
- c# - 如何使用 facebook graph api 创建对象,用于统一游戏?
- javascript - 按键事件不适用于 PrintScreen 或 Windows 键
- python - 将 3D 绘图的轴放置在图形内
- java - 什么是引导日历以及如何从引导日历中选择一个日期,该日期在使用 java 的 selenium Web 驱动程序中只读