通用协议类型参数与直接协议类型的区别
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 目前在所有情况下都禁止它。
这是我的游乐场代码:
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 目前在所有情况下都禁止它。