Protocol 只能作为泛型约束,因为它有 Self 或 associatedType 要求
Protocol can only be used as a generic constraint because it has Self or associatedType requirements
我有一个协议 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 要求。
假设我们现在调整您的协议以添加使用关联类型的例程:
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(或结构或枚举),将真实类型与抽象模型类型名称相关联:
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
现在声明一个 LegoRequestType
的数组是完全合理的,因为如果我们想要 frobulate
所有这些,我们知道我们必须每次都传入一个 LegoModel
。
关联类型的这种细微差别使得任何使用它们的协议都很特别。 Swift 标准库有这样的协议,最值得注意的是 Collection
或 Sequence
。
为了允许您创建一个实现 Collection
协议的事物数组或一组实现序列协议的事物,标准库采用了一种称为 "type-erasure" 的技术来创建结构输入 AnyCollection<T>
或 AnySequence<T>
。类型擦除技术在 Stack Overflow 的回答中解释起来相当复杂,但如果你在网上搜索,就会有很多关于它的文章。
我可以在 YouTube 上推荐来自 Alex Gallagher on Protocols With Associated Types (PATs) 的视频。
对您的代码设计稍加改动就可以实现。在协议层次结构的顶部添加一个空的 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)
从 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
关键字使类型 opaque 将解决问题,通常这就是我们唯一想要的:
var objectA: some ProtocolA = ClassA()
以下情况也可能出现此错误:
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
}
Swift 5.1
示例如何通过实现关联类型和[来使用通用协议 =19=]基础协议:
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
}
我有一个协议 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 要求。
假设我们现在调整您的协议以添加使用关联类型的例程:
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(或结构或枚举),将真实类型与抽象模型类型名称相关联:
class LegoRequestType: RequestType {
typealias Model = LegoModel
// Implement protocol requirements here
}
现在声明一个 LegoRequestType
的数组是完全合理的,因为如果我们想要 frobulate
所有这些,我们知道我们必须每次都传入一个 LegoModel
。
关联类型的这种细微差别使得任何使用它们的协议都很特别。 Swift 标准库有这样的协议,最值得注意的是 Collection
或 Sequence
。
为了允许您创建一个实现 Collection
协议的事物数组或一组实现序列协议的事物,标准库采用了一种称为 "type-erasure" 的技术来创建结构输入 AnyCollection<T>
或 AnySequence<T>
。类型擦除技术在 Stack Overflow 的回答中解释起来相当复杂,但如果你在网上搜索,就会有很多关于它的文章。
我可以在 YouTube 上推荐来自 Alex Gallagher on Protocols With Associated Types (PATs) 的视频。
对您的代码设计稍加改动就可以实现。在协议层次结构的顶部添加一个空的 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)
从 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
关键字使类型 opaque 将解决问题,通常这就是我们唯一想要的:
var objectA: some ProtocolA = ClassA()
以下情况也可能出现此错误:
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
}
Swift 5.1
示例如何通过实现关联类型和[来使用通用协议 =19=]基础协议:
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
}