协议只能用作通用约束,因为它具有 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 标准库有这样的协议,尤其是 Collection
或 Sequence
。
为了允许您创建实现Collection
协议的事物数组或实现序列协议的一组事物,标准库采用了一种称为“类型擦除”的技术来创建结构类型AnyCollection<T>
或AnySequence<T>
。类型擦除技术在 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 或关联的类型要求