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

Posted

技术标签:

【中文标题】协议只能用作通用约束,因为它具有 Self 或 associatedType 要求【英文标题】:Protocol can only be used as a generic constraint because it has Self or associatedType requirements 【发布时间】:2016-04-01 04:29:49 【问题描述】:

我有一个协议 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 要求。

【问题讨论】:

【参考方案1】:

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

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 对歧义不满意,因此它不允许您声明具有关联类型的协议变量。

同时,例如,当我们知道它们都使用LegoModel 时,创建一个RequestType 数组是非常有意义的。这似乎是合理的,确实如此,但您需要某种方式来表达这一点。

一种方法是创建一个将真实类型与抽象模型类型名称相关联的类(或结构或枚举):

class LegoRequestType: RequestType 
  typealias Model = LegoModel

  // Implement protocol requirements here

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

关联类型的这种细微差别使得任何使用它们的协议都变得特别。 Swift 标准库有这样的协议,尤其是 CollectionSequence

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

我可以在 YouTube 上推荐来自 Alex Gallagher on Protocols With Associated Types (PATs) 的视频。

【讨论】:

"您的解决方案非常通用" ? 这是我见过的对这个问题最好的解释之一 这么好的解释,这么单一的答案。 frobulate 是什么意思? 在 1980 年代有一个文字冒险游戏系列,以游戏 Zork 开始。在那个游戏系列中有 Frobozz 魔术公司。他们过去常常把事情搞砸。简而言之,对于不特定的操作来说,这是一个愚蠢的短语。【参考方案2】:

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

【讨论】:

注意:ios 13.0.0 或更新版本支持。【参考方案3】:

斯威夫特 5.1

一个示例如何通过实现关联类型基本协议来使用通用协议:: p>

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

    

【讨论】:

【参考方案4】:

对您的代码设计稍作改动就可以实现这一目标。在协议层次结构的顶部添加一个空的、非关联类型的协议。像这样……

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)

【讨论】:

【参考方案5】:

以下情况也可能出现此错误:

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

【讨论】:

以上是关于协议只能用作通用约束,因为它具有 Self 或 associatedType 要求的主要内容,如果未能解决你的问题,请参考以下文章

“协议......只能用作通用约束,因为它具有 Self 或关联的类型要求”是啥意思?

Protocol 'View' 只能用作通用约束,因为它具有 Self 或关联的类型要求

为啥我会收到错误“协议……只能用作通用约束,因为它具有自身或关联的类型要求”?

协议中的共享实例属性

协议一致性检查

如何在 swiftUI 中创建一个普通视图