假设我们现在调整了您的协议,添加了一个使用关联类型的例程:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
如果Swift允许你按照自己的方式创建RequestType
数组。我可以将这些请求类型的数组传递到函数中:
func handleQueueOfRequests(queue: [RequestType]) {
for request in queue {
request.frobulateModel()
}
}
我想要 frobulate 所有的事情,但是我需要知道传递到调用中的参数类型。我的一些 RequestType 实体可以使用 LegoModel,一些可以使用 PlasticModel,而另一些则可以使用 PeanutButterAndPeepsModel。Swift 对于这种不确定性感到不满,因此它不允许您声明具有关联类型的协议变量。
同时,例如创建一个 RequestType 数组时,我们知道所有的 RequestType 都使用 LegoModel,这似乎是合理的,实际上也确实如此,但您需要某种方式来表达它。
一种方法是创建一个将真实类型与抽象 Model 类型名称相关联的类(或结构体、枚举):
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
现在,声明一个
LegoRequestType
的数组是完全合理的,因为如果我们想要对它们进行
frobulate
,我们知道每次都必须传入一个
LegoModel
。
这种关联类型的细微差别使得任何使用它们的协议都很特殊。Swift标准库有一些这样的协议,最著名的是
Collection
或
Sequence
。
为了让您创建一个实现
Collection
协议的东西数组,或者一组实现序列协议的东西集合,标准库采用了一种称为“类型擦除”的技术来创建结构体类型
AnyCollection<T>
或
AnySequence<T>
。类型擦除技术在Stack Overflow答案中解释起来相当复杂,但如果您搜索网络,会发现有很多文章可以阅读。
Swift 5.7的存在类型。
Swift 5.7引入了使用
any
关键字的显式存在类型。这将消除“协议只能用作泛型约束”的错误,但它并不能解决这个示例中的根本问题。 (诚然,这个示例是学术性的,仅用于演示目的,并且由于其限制,在实际代码中可能没有用处。但它也展示了显式存在类型并不是万能药。)
下面是使用Swift 5.7和
any
关键字的代码示例。
public protocol RequestType: AnyObject {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
func handleQueueOfRequests(queue: [any RequestType]) {
for request in queue {
request.frobulateModel()
}
}
现在我们的队列包含了一组存在值,我们不再有关于“类型不能在这里使用,因为Self或AssociatedType约束”的错误。但是它并没有解决这个例子中的潜在问题,因为frobulateModel方法仍然可以接受任意类型(符合RequestType协议的实体相关类型)。
Swift提供了其他机制来帮助弥补这一点。通常情况下,您需要限制Model值以暴露所有Models共享的行为。frobulateModel方法可以被泛型化,并对参数进行约束以遵循该协议。或者您可以使用Swift 5.7的主要关联类型(
SE-0346)来帮助约束协议级别的Models行为。
因此,显式存在值可以消除OP所问的错误消息,但它们并不是每种情况的解决方案。
此外,请记住,存在值会导致间接性,从而可能引入性能问题。在他们的WWDC会议上,Apple警告我们要谨慎使用它们。