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


101

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

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要求,因此只能用作通用约束。

Answers:


152

假设目前我们调整您的协议以添加使用关联类型的例程:

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类型名称相关联的类(或struct或enum):

class LegoRequestType: RequestType {
  typealias Model = LegoModel

  // Implement protocol requirements here
}

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

与关联类型的这种细微差别使使用它们的协议变得特别。Swift标准库最著名的协议就是这样的CollectionSequence

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

我可以推荐来自Alex Gallagher的有关YouTube上具有关联类型协议(PAT)的视频。


40
“您的解决方案非常通用
Adolfo

6
这是我所见过的关于此问题的最佳解释之一
Keab42 '17

1
那么好的解释,那么一个答案。
阿尔玛斯·阿迪贝克

1
弄乱是什么意思?
Mofawaw

1
这个答案是挫败frobulate一词的重要借口。
ScottyBlades

16

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

4

在代码设计上稍作更改即可使其成为可能。在协议层次结构的顶部添加一个空的,非associatedType协议。像这样...

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)

4

斯威夫特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

    
}

0

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

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
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.