【问题标题】:Protocol can only be used as a generic constraint because it has Self or associatedType requirementsProtocol 只能用作通用约束,因为它具有 Self 或 associatedType 要求
【发布时间】:2020-09-16 18:53:54
【问题描述】:

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

【问题讨论】:

    标签: ios swift generics swift2 swift-protocols


    【解决方案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
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2016-08-16
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多