简体   繁体   English

协议只能用作通用约束,因为它具有 Self 或 associatedType 要求

[英]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 标准库有这样的协议,其中最著名的是CollectionSequence

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 Existentials Swift 5.7 存在

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 告诫我们要明智地使用它们。

From Swift 5.1 - Xcode 11从 Swift 5.1 - Xcode 11

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()

Swift 5.1斯威夫特 5.1

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
}

Existential 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关键字来解决“此协议不能用作通用约束,因为它具有SelfassociatedType要求”:

let queue = [any RequestType]()

Xcode 14 now suggests this change as a fix-it, and the error disappears! Xcode 14 现在建议将此更改作为修复它,并且错误消失了!

Caveat: Use improved generic syntax where possible警告:尽可能使用改进的通用语法

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.

相关问题 协议只能用作通用约束,因为它具有 Self 或相关的类型要求 - Protocol can only be used as a generic constraint because it has Self or associated type requirements `Protocol的问题只能用作通用约束,因为它具有Self或相关类型要求 - Issue with `Protocol can only be used as a generic constraint because it has Self or associated type requirements` 协议“视图”只能用作通用约束,因为它具有 Self 或关联类型要求 - Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements 协议“行”只能用作通用约束,因为它具有“自身”或关联的类型要求 - Protocol 'Line' can only be used as a generic constraint because it has Self or associated type requirements 协议“ xxx”只能用作一般约束,因为它具有“自身”或相关类型要求 - Protocol 'xxx' can only be used as a generic constraint because it has Self or associated type requirements Swift UI 在协议中使用视图(协议“视图”只能用作通用约束,因为它具有自我或关联类型要求) - Swift UI use View in a Protocol (Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements) 我该如何修复Protocol只能用作通用约束,因为它具有Self或关联的类型需求错误 - How can I fix Protocol can only be used as a generic constraint because it has Self or associated type requirements error 属性中的协议,错误协议只能用作通用约束 - Protocol in property, error Protocol can only be used as a generic constraint 形状数组 - 协议只能用作通用约束 - Array of shapes - Protocol can only be used as a generic constraint 如何实现一个通用协议,它具有一个使用关联类型的函数? - How to implement to a generic protocol, which has a function using the Type of associatedtype?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM