[英]Protocol can only be used as a generic constraint because it has Self or associatedType requirements
I have a protocol RequestType and it has associatedType Model as below.我有一个协议 RequestType ,它有如下关联类型模型。
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
}
public extension RequestType {
public func executeRequest(completionHandler: Result<Model, NSError> -> Void) {
request.response(rootKeyPath: rootKeyPath) { [weak self] (response: Response<Model, NSError>) -> Void in
completionHandler(response.result)
guard let weakSelf = self else { return }
if weakSelf.logging { debugPrint(response) }
}
}
}
Now I am trying to make a queue of all failed requests.现在我正在尝试为所有失败的请求创建一个队列。
public class RequestEventuallyQueue {
static let requestEventuallyQueue = RequestEventuallyQueue()
let queue = [RequestType]()
}
But I get the error on line let queue = [RequestType]()
that Protocol RequestType can only be used as a generic constraint because it has Self or associatedType requirements.但是我在let queue = [RequestType]()
线上得到错误,即 Protocol RequestType 只能用作通用约束,因为它具有 Self 或 associatedType 要求。
Suppose for the moment we adjust your protocol to add a routine that uses the associated type:假设我们现在调整您的协议以添加一个使用关联类型的例程:
public protocol RequestType: class {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
And Swift were to let you create an array of RequestType
the way you want to. Swift 允许你按照你想要的方式创建一个RequestType
数组。 I could pass an array of those request types into a function:我可以将这些请求类型的数组传递给一个函数:
func handleQueueOfRequests(queue: [RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
I get down to the point that I want to frobulate all the things, but I need to know what type of argument to pass into the call.我想把所有事情都搞砸了,但我需要知道要传递给调用的参数类型。 Some of my RequestType
entities could take a LegoModel
, some could take a PlasticModel
, and others could take a PeanutButterAndPeepsModel
.我的一些RequestType
实体可以采用LegoModel
,有些可以采用PlasticModel
,而其他的可以采用PeanutButterAndPeepsModel
。 Swift is not happy with the ambiguity so it will not let you declare a variable of a protocol that has an associated type. Swift 对这种歧义并不满意,因此它不允许您声明具有关联类型的协议变量。
At the same time it makes perfect sense to, for example, create an array of RequestType
when we KNOW that all of them use the LegoModel
.同时,例如,当我们知道它们都使用LegoModel
时,创建一个RequestType
数组是非常有意义的。 This seems reasonable, and it is, but you need some way to express that.这似乎是合理的,而且确实如此,但是您需要某种方式来表达这一点。
One way to do that is to create a class (or struct, or enum) that associates a real type with the abstract Model type name:一种方法是创建一个将真实类型与抽象模型类型名称相关联的类(或结构或枚举):
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
Now it's entirely reasonable to declare an array of LegoRequestType
because if we wanted to frobulate
all of them we know we would have to pass in a LegoModel
each time.现在声明一个LegoRequestType
数组是完全合理的,因为如果我们想对所有这些数组进行混淆,我们知道LegoModel
frobulate
This nuance with Associated Types makes any protocol that uses them special.与关联类型的这种细微差别使任何使用它们的协议都变得特别。 The Swift Standard Library has Protocols like this most notably Collection
or Sequence
. Swift 标准库有这样的协议,其中最著名的是Collection
或Sequence
。
To allow you to create an array of things that implement the Collection
protocol or a set of things that implement the sequence protocol, the Standard Library employs a technique called "type-erasure" to create the struct types AnyCollection<T>
or AnySequence<T>
.为了允许您创建实现Collection
协议的事物数组或实现序列协议的事物集合,标准库采用了一种称为“类型擦除”的技术来创建结构类型AnyCollection<T>
或AnySequence<T>
。 The type-erasure technique is rather complex to explain in a Stack Overflow answer, but if you search the web there are lots of articles about it.类型擦除技术在 Stack Overflow 答案中解释起来相当复杂,但如果你在网上搜索,就会有很多关于它的文章。
Swift 5.7 introduces explicit existential using the any
keyword. Swift 5.7 使用any
关键字引入了显式存在。 This will remove the "Protocol can only be used as a generic constraint…" error, but it doesn't solve the fundamental problem with this example .这将消除“Protocol can only be used as a generic constraint...”错误,但并不能解决本示例的根本问题。 (Admittedly this example is academic, for demonstration purposes, and likely not useful in real code because of its limitations. But it also demonstrates how explicit existentials aren't a panacea.) (诚然,这个例子是学术性的,用于演示目的,并且由于其局限性,可能在实际代码中没有用。但它也说明了显式存在不是万能的。)
Here is the code sample using Swift 5.7 and the any
keyword.这是使用 Swift 5.7 和any
关键字的代码示例。
public protocol RequestType: AnyObject {
associatedtype Model
var path: String { get set }
func frobulateModel(aModel: Model)
}
func handleQueueOfRequests(queue: [any RequestType]) {
// frobulate All The Things!
for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}
Now our queue contains a collection of existentials and we no longer have the error about the "type cannot be used here because of Self or AssociatedType constraints".现在我们的队列包含一个existentials的集合,我们不再有关于“由于Self或AssociatedType约束而无法在此处使用类型”的错误。 But it doesn't fix the underlying problem in this example because the frobulateModel
method can still take an arbitrary type (the associated type of the entity conforming to the RequestType
protocol).但是它并没有解决这个例子中的根本问题,因为frobulateModel
方法仍然可以采用任意类型(实体的关联类型符合RequestType
协议)。
Swift provides other mechanisms that can help compensate for this. Swift 提供了其他机制来帮助弥补这一点。 In general you'd want constrain the Model
value to expose behavior shared by all Models
.通常,您希望约束Model
值以公开所有Models
共享的行为。 The frobulateModel
method might be made generic and have constraints on the parameter to follow that protocol. frobulateModel
方法可能是通用的,并且对遵循该协议的参数有限制。 Or you could use Swift 5.7's primary associated types ( SE-0346 ) to help constrain the behavior of Models
at the protocol level.或者,您可以使用 Swift 5.7 的主要关联类型 ( SE-0346 ) 来帮助在协议级别约束Models
的行为。
So yes, explicit existentials can remove the error message that the OP asked about - but they are not a solution for every situation.所以是的,显式存在可以删除 OP 询问的错误消息 - 但它们并不是每种情况的解决方案。
Also, keep in mind that existentials lead to indirection and that can introduce performance problems.另外,请记住,existentials 会导致间接性,这可能会导致性能问题。 In their WWDC session, Apple cautioned us to use them judiciously.在他们的 WWDC 会议上,Apple 告诫我们要明智地使用它们。
You can use an opaque result type to achieve something like that.您可以使用不透明的结果类型来实现类似的效果。
imagine this:想象一下:
protocol ProtocolA {
associatedtype number
}
class ClassA: ProtocolA {
typealias number = Double
}
So the following generates the error:所以下面会产生错误:
var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */
But making the type opaque by adding the some
keyword before the type will fix the issue and usually thats the only thing we want:但是通过在类型之前添加some
关键字来使类型不透明将解决问题,通常这是我们唯一想要的:
var objectA: some ProtocolA = ClassA()
An example how you can use generic protocols by implementing an associated type and base protocol :一个如何通过实现关联类型和基本协议来使用通用协议的示例:
import Foundation
protocol SelectOptionDataModelProtocolBase: class{}
protocol SelectOptionDataModelProtocol: SelectOptionDataModelProtocolBase {
associatedtype T
var options: Array<T> { get }
var selectedIndex: Int { get set }
}
class SelectOptionDataModel<A>: SelectOptionDataModelProtocol {
typealias T = A
var options: Array<T>
var selectedIndex: Int
init(selectedIndex _selectedIndex: Int, options _options: Array<T>) {
self.options = _options
self.selectedIndex = _selectedIndex
}
}
And an example View Controller:还有一个示例视图控制器:
import UIKit
struct Car {
var name: String?
var speed: Int?
}
class SelectOptionViewController: UIViewController {
// MARK: - IB Outlets
// MARK: - Properties
var dataModel1: SelectOptionDataModelProtocolBase?
var dataModel2: SelectOptionDataModelProtocolBase?
var dataModel3: SelectOptionDataModelProtocolBase?
// MARK: - Initialisation
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
convenience init() {
self.init(title: "Settings ViewController")
}
init(title _title: String) {
super.init(nibName: nil, bundle: nil)
self.title = _title
self.dataModel1 = SelectOptionDataModel<String>(selectedIndex: 0, options: ["option 1", "option 2", "option 3"])
self.dataModel2 = SelectOptionDataModel<Int>(selectedIndex: 0, options: [1, 2, 3])
self.dataModel3 = SelectOptionDataModel<Car>(selectedIndex: 0, options: [Car(name: "BMW", speed: 90), Car(name: "Toyota", speed: 60), Car(name: "Subaru", speed: 120)])
}
// MARK: - IB Actions
// MARK: - View Life Cycle
}
A little change in design of your code could make it possible.对代码设计稍作改动就可以实现。 Add an empty, non-associatedType, protocol at the top of your protocol hierarchy.在协议层次结构的顶部添加一个空的、非关联类型的协议。 Like this...像这样...
public protocol RequestTypeBase: class{}
public protocol RequestType: RequestTypeBase {
associatedtype Model
var path: Model? { get set } //Make it type of Model
}
public class RequestEventuallyQueue {
static let requestEventuallyQueue = RequestEventuallyQueue()
var queue = [RequestTypeBase]() //This has to be 'var' not 'let'
}
Another example, with classes derived from the protocol RequestType, making a queue and passing the queue to a function to print appropriate type另一个示例,使用从协议 RequestType 派生的类,创建队列并将队列传递给函数以打印适当的类型
public class RequestA<AType>: RequestType{
public typealias Model = AType
public var path: AType?
}
public class RequestB<BType>: RequestType{
public typealias Model = BType
public var path: BType?
}
var queue = [RequestTypeBase]()
let aRequest: RequestA = RequestA<String>()
aRequest.path = "xyz://pathA"
queue.append(aRequest)
let bRequest: RequestB = RequestB<String>()
bRequest.path = "xyz://pathB"
queue.append(bRequest)
let bURLRequest: RequestB = RequestB<URL>()
bURLRequest.path = URL(string: "xyz://bURLPath")
queue.append(bURLRequest)
func showFailed(requests: [RequestTypeBase]){
for request in requests{
if let request = request as? RequestA<String>{
print(request.path!)
}else if let request = request as? RequestB<String>{
print(request.path!)
}else if let request = request as? RequestB<URL>{
print(request.path!)
}
}
}
showFailed(requests: queue)
This error may also occur in the following scenario:在以下情况下也可能出现此错误:
protocol MyProtocol {
assosciatedtype SomeClass
func myFunc() -> SomeClass
}
struct MyStuct {
var myVar = MyProtocol
}
In this case, all you have to do to fix the issue is to use generics:在这种情况下,您所要做的就是使用泛型来解决这个问题:
protocol MyProtocol {
assosciatedtype SomeClass
func myFunc() -> SomeClass
}
struct MyStuct<T: MyProtocol> {
var myVar = T
}
any
in Swift 5.7 🪛 Swift 5.7 中存在的any
🪛 We can now solve the "This protocol cannot be used as a generic constraint because it has Self
or associatedType
requirements" by simply using the any
keyword at the call site:我们现在可以通过在调用站点简单地使用any
关键字来解决“此协议不能用作通用约束,因为它具有Self
或associatedType
要求”:
let queue = [any RequestType]()
Xcode 14 now suggests this change as a fix-it, and the error disappears! Xcode 14 现在建议将此更改作为修复它,并且错误消失了!
Generics are more fully-featured and performant than existential any
at the moment, so we may prefer to use existential any
despite its limitations.泛型目前比existential any
功能更全面,性能更好,因此尽管存在限制,我们可能更喜欢使用existential any
。
To make it easier to use the right generic syntax, we can use the some
keyword to specify generics for functions with a single generic argument (this is called a primary associated type).为了更容易使用正确的泛型语法,我们可以使用some
关键字为具有单个泛型参数的函数指定泛型(这称为主关联类型)。
func addEntries1(_ entries: some Collection<MailmapEntry>, to mailmap: inout some Mailmap) {
for entry in entries {
mailmap.addEntry(entry)
}
}
func addEntries2(_ entries: any Collection<MailmapEntry>, to mailmap: inout any Mailmap) {
for entry in entries {
mailmap.addEntry(entry)
}
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.