我有一个协议RequestType,它有关联的Type Model,如下所示。
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要求。
答案 0 :(得分:97)
假设我们暂时调整您的协议以添加使用相关类型的例程:
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
数组是完全合理的。这似乎是合理的,但是,你需要某种方式来表达它。
这样做的一种方法是创建一个将真实类型与抽象模型类型名称相关联的类(或结构或枚举):
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
现在声明一个LegoRequestType
数组是完全合理的,因为如果我们想要frobulate
所有这些数组,我们知道每次都需要传入LegoModel
关联类型的这种细微差别使得使用它们的任何协议都很特殊。 Swift标准库具有这样的协议,尤其是Collection
或Sequence
。
为了允许您创建实现Collection
协议的一系列事物或一组实现序列协议的事物,标准库采用了一种名为&#34; type-erasure&#34;创建结构类型AnyCollection<T>
或AnySequence<T>
。类型擦除技术在Stack Overflow答案中解释相当复杂,但是如果你在网上搜索有很多关于它的文章。
我可以在YouTube上推荐来自Alex Gallagher on Protocols With Associated Types (PATs)的视频。
答案 1 :(得分:5)
一个示例,如何通过实现关联类型和基本协议来使用通用协议:>
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
}
答案 2 :(得分:1)
您可以使用不透明结果类型来实现类似目的。
想象一下:
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()
答案 3 :(得分:0)
对代码设计进行一些小的更改即可使其成为可能。在协议层次结构的顶部添加一个空的non-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 :(得分: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
}