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

zvms9eto  于 2022-11-06  发布在  Swift
关注(0)|答案(6)|浏览(197)

我有一个协议RequestType,它有如下的associatedType模型。

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

x0fgdtte

x0fgdtte1#

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

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时,这看起来很合理,确实如此,但你需要某种方式来表达它。
一种方法是创建一个类(或结构,或枚举),将一个真实的类型与抽象Model类型名称相关联:

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

现在声明一个LegoRequestType数组是完全合理的,因为如果我们想对所有的frobulate数组进行声明,我们知道每次都必须传入一个LegoModel
Swift标准库中有类似的协议,最著名的是CollectionSequence
为了允许您创建一个实现Collection协议的数组或一组实现序列协议的对象,标准库采用了一种称为“类型擦除”的技术来创建结构体类型AnyCollection<T>AnySequence<T>。类型擦除技术在“堆栈溢出”答案中解释起来相当复杂,但如果你搜索网络,有很多关于它的文章。

Swift 5.7存在性

Swift 5.7使用any关键字引入了显式存在,这将消除“Protocol can be only used as a generic constraint...”错误,但它并没有解决 * 本例 * 的基本问题。(诚然,这个例子是学术性的,出于演示的目的,而且由于其局限性,在真实的代码中可能没有用。但它也说明了显式存在不是万能的。)
下面是使用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? */)
    }
}

现在,我们的队列包含了一个存在体集合,我们不再有“type cannot be used here because of Self or AssociatedType constraints”的错误。但是,它并没有解决这个例子中的底层问题,因为frobulateModel方法仍然可以接受任意类型(符合RequestType协议的实体的关联类型)。
Swift提供了其他机制来帮助弥补这一点。一般来说,您需要约束Model的值以公开所有Models共享的行为。frobulateModel方法可能会成为泛型,并对参数进行约束以遵循该协议。或者,您可以使用Swift 5.7的主要关联类型(SE-0346),以帮助在协议级别约束Models的行为。
因此,是的,显式存在可以删除OP所要求的错误消息--但它们并不是每种情况的解决方案。
另外,请记住,存在主义会导致间接性,这可能会带来性能问题。在他们的WWDC会议上,苹果警告我们要明智地使用它们。

gcxthw6b

gcxthw6b2#

来自Swift 5.1 - Xcode 11

您可以使用opaque结果类型来实现类似的功能。
想象一下:

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

2fjabf4q3#

雨燕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

}
y3bcpkx1

y3bcpkx14#

在你的代码设计上做一点小小的改变就可以做到这一点。在你的协议层次结构的顶部添加一个空的、非关联类型的协议。就像这样...

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

gj3fmq9x5#

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

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
}
ycggw6v2

ycggw6v26#

Swift 5.7中存在的any🪛

现在,我们可以通过在调用点使用any关键字来解决“此协议不能用作通用约束,因为它具有SelfassociatedType要求”的问题:

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

相关问题