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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/36348061/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 08:50:07  来源:igfitidea点击:

Protocol can only be used as a generic constraint because it has Self or associatedType requirements

iosswiftgenericsswift2swift-protocols

提问by Rahul Katariya

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 要求。

回答by Scott Thompson

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 RequestTypethe way you want to. I could pass an array of those request types into a function:

Swift 允许你RequestType以你想要的方式创建一个数组。我可以将这些请求类型的数组传递给函数:

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 RequestTypeentities could take a LegoModel, some could take a PlasticModel, and others could take a 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.

我想把所有事情都搞定,但我需要知道要传递到调用中的参数类型。我的一些RequestType实体可以采用 a LegoModel,一些可以采用 a PlasticModel,而其他一些可以采用PeanutButterAndPeepsModel。Swift 对这种歧义并不满意,因此它不会让您声明具有关联类型的协议的变量。

At the same time it makes perfect sense to, for example, create an array of RequestTypewhen we KNOW that all of them use the LegoModel. This seems reasonable, and it is, but you need some way to express that.

同时,例如,RequestType当我们知道所有这些都使用LegoModel. 这似乎是合理的,而且确实如此,但是您需要某种方式来表达这一点。

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 LegoRequestTypebecause if we wanted to frobulateall of them we know we would have to pass in a LegoModeleach time.

现在声明一个数组是完全合理的,LegoRequestType因为如果我们想要frobulate所有这些,我们知道我们LegoModel每次都必须传入 a 。

This nuance with Associated Types makes any protocol that uses them special. The Swift Standard Library has Protocols like this most notably Collectionor Sequence.

关联类型的这种细微差别使得使用它们的任何协议都很特别。Swift 标准库中最显着的就是像这样的协议CollectionSequence.

To allow you to create an array of things that implement the Collectionprotocol 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>. 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.

为了允许您创建一组实现Collection协议的事物或一组实现序列协议的事物,标准库采用了一种称为“类型擦除”的技术来创建结构类型AnyCollection<T>AnySequence<T>. 类型擦除技术在 Stack Overflow 的回答中解释起来相当复杂,但如果你在网上搜索,有很多关于它的文章。

I can recommend a video from Alex Gallagher on Protocols With Associated Types (PATs)on YouTube.

我可以在 YouTube推荐Alex Gallagher的一段关于关联类型协议 (PAT)的视频。

回答by Mojtaba Hosseini

From Swift 5.1

从 Swift 5.1

You can use an opaqueresult 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 opaqueby adding the somekeyword before the type will fix the issue and usually thats the only thing we want:

但是通过在类型之前添加关键字来使类型不透明some将解决问题,通常这是我们唯一想要的:

var objectA: some ProtocolA = ClassA()

回答by Farhan Arshad

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)

回答by TimBigDev

Swift 5.1

斯威夫特 5.1

An examplehow you can use generic protocolsby implementing an associated typeand 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


}

回答by Lew Winczynski

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
}