协议只能用作泛型约束,因为它具有 Self 或 AssociatedType 要求

我有一个 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) }
}
}


}

现在,我尝试为所有失败的请求创建一个队列。

public class RequestEventuallyQueue {


static let requestEventuallyQueue = RequestEventuallyQueue()
let queue = [RequestType]()


}

但是我在 let queue = [RequestType]()行得到一个错误: Protocol RequestType 只能用作通用约束,因为它具有 Self 或 AssociatedType 需求。

102335 次浏览

假设我们调整您的协议,添加一个使用关联类型的例程:

public protocol RequestType: class {
associatedtype Model
var path: String { get set }
    

func frobulateModel(aModel: Model)
}

Swift 将允许您按照您希望的方式创建 RequestType数组。我可以将这些请求类型的数组传递到函数中:

func handleQueueOfRequests(queue: [RequestType]) {
// frobulate All The Things!


for request in queue {
request.frobulateModel(/* What do I put here? */)
}
}

我想说的是,我想让所有的事情都变得复杂,但是我需要知道传递到调用中的参数类型。我的一些 RequestType实体可以采取一个 LegoModel,一些可以采取一个 PlasticModel,其他可以采取一个 PeanutButterAndPeepsModel。Swift 不喜欢这种模糊性,所以它不允许您声明具有关联类型的协议的变量。

同时,这是完全有意义的,例如,创建一个数组的 RequestType,当我们知道他们都使用的 LegoModel。这似乎是合理的,而且确实如此,但是你需要一些方法来表达这一点。

一种方法是创建一个类(或 struct,或 enum) ,它将一个实际类型与抽象 Model 类型名相关联:

class LegoRequestType: RequestType {
typealias Model = LegoModel


// Implement protocol requirements here
}

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

与关联类型的这种细微差别使得使用它们的任何协议都具有特殊性。Swift 标准库具有这样的协议,其中最著名的是 CollectionSequence

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

Swift 5.7存在主义

Swift 5.7使用 any关键字引入了显式存在主义。这将删除“ Protocol can only be used as a general 約束...”错误,但它不能解决基本问题 用这个例子。(不可否认,出于演示的目的,此示例是学术性的,并且由于其局限性,可能在实际代码中没有用处。但它也证明了明确的存在主义并不是万能药。)

下面是使用 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? */)
}
}

现在我们的队列包含了一个存在类型的集合,我们不再有“由于 Self 或 AssociatedType 约束类型不能在这里使用”的错误。但是它没有解决本例中的潜在问题,因为 frobulateModel方法仍然可以采用任意类型(符合 RequestType协议的实体的相关类型)。

Swift 提供了其他机制,可以帮助弥补这一缺陷。通常,您希望约束 Model值以公开所有 Models共享的行为。frobulateModel方法可以是泛型的,并且在参数上有约束以遵循该协议。或者您可以使用 Swift 5.7的主要关联类型(SE-0346)来帮助在协议级别约束 Models的行为。

因此,是的,显式存在可以删除 OP 询问的错误消息-但是它们不是每种情况的解决方案。

另外,请记住存在性导致间接性,这可能会引入性能问题。在 WWDC 会议上,苹果提醒我们要明智地使用它们。

代码设计上的一点小改动就可以使之成为可能。在协议层次结构的顶部添加一个空的、非关联的 Type 协议。像这样..。

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'


}

另一个示例,使用从 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)

来自 Swift 5.1-Xcode 11

您可以使用 不透明结果类型来实现类似的功能。

想象一下:

protocol ProtocolA {
associatedtype number
}


class ClassA: ProtocolA {
typealias number = Double
}

因此,下面的代码会生成错误:

var objectA: ProtocolA = ClassA() /* Protocol can only be used as a generic constraint because it has Self or associatedType requirements */

但是通过在类型之前添加 some关键字来创建 不透明类型可以解决这个问题,而且通常这是我们唯一想要的:

var objectA: some ProtocolA = ClassA()

此错误也可能发生在下列情况中:

protocol MyProtocol {
assosciatedtype SomeClass
func myFunc() -> SomeClass
}


struct MyStuct {
var myVar = MyProtocol
}

在这种情况下,要解决这个问题,您所要做的就是使用泛型:

protocol MyProtocol {
assosciatedtype SomeClass
func myFunc() -> SomeClass
}


struct MyStuct<T: MyProtocol> {
var myVar = T
}

Swift 5.1

例子如何通过实现 相关类型基地协议来使用 通用协议:

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
}
    

}

以下是一个视图控制器的例子:

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


    

}

Swift 5.7中的存在主义 any

我们现在可以解决“这个协议不能用作通用约束,因为它有 SelfassociatedType的要求”,只需在调用站点使用 any关键字:

let queue = [any RequestType]()

Xcode 14现在建议将此更改作为补丁,然后错误就消失了!

注意: 尽可能使用改进的通用语法

泛型目前比存在 any功能更全面,性能更好,所以我们可能更愿意使用存在 any,尽管它的局限性。

为了更容易使用正确的泛型语法,我们可以使用 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)
}
}