我有一个协议:
protocol CustomProtocol {
var title: String { get }
var subtitle: String { get }
}
然后我有2个符合这个协议的对象。我想比较它们,所以我想CustomProtocol是Equatable。
protocol CustomProtocol: Equatable {
var title: String { get }
var subtitle: String { get }
static func ==(lhs: Self, rhs: Self) -> Bool
}
extension CustomProtocol {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.title == rhs.title
}
}
但是在那次改变之后我得到了#34;协议CustomProtocol只能用作通用约束,因为它具有Self或相关类型的requeriments。 我能想到解决这个问题的唯一方法是让第三个属性像哈希一样依赖于其他属性并比较这个属性。
带有实际代码的答案 0 :(得分:1)
Equatable协议有一个自我约束来解决你应该只能检查相同类型的对象之间的相等性而不是相同协议的问题。这就是为什么它有自我要求。否则你可以说
let a: Equatable = 42
let b: Equatable = "hello"
和a == b
会奏效。这可能很糟糕,因为您可以比较完全不相关类型的对象。自我要求使这成为编译时错误。
如果您想在协议的基础上比较对象,只需在没有自我要求的情况下实现==
运算符:
extension CustomProtocol {
func == (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
return lhs.name == rhs.name
}
func != (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
return !(lhs == rhs)
}
}
现在,您可以使用CustomProtocol
类型直接声明协议实例并进行比较。
但在这种情况下,协议可能不是正确的抽象。也许你应该把它作为一个抽象类来实现。
答案 1 :(得分:1)
由于Equatable
具有Self
要求,因此不应直接在协议上实施。否则,协议将无法用作类型。
要在协议级别实现Equatable
但是能够将协议用作类型,您可以使用类型擦除。
为了演示,我修改了你的游乐场中给出的代码来构建一个类型的橡皮擦。
有关我使用的方法的详细说明,请查看我的博客上的这篇文章:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/
以下是您操场上修改后的代码:
protocol CustomProtocol {
var title: String { get }
var subtitle: String { get }
func isEqualTo(_ other: CustomProtocol) -> Bool
func asEquatable() -> AnyEquatableCustomProtocol
}
extension CustomProtocol where Self: Equatable {
func isEqualTo(_ other: CustomProtocol) -> Bool {
guard let o = other as? Self else { return false }
return self == o
}
func asEquatable() -> AnyEquatableCustomProtocol {
return AnyEquatableCustomProtocol(self)
}
}
struct A: CustomProtocol, Equatable {
var title: String
var subtitle: String
static func ==(lhs: A, rhs: A) -> Bool {
return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
}
}
struct B: CustomProtocol, Equatable {
var title: String
var subtitle: String
static func ==(lhs: B, rhs: B) -> Bool {
return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
}
}
struct AnyEquatableCustomProtocol: CustomProtocol, Equatable {
var title: String { return value.title }
var subtitle: String { return value.subtitle }
init(_ value: CustomProtocol) { self.value = value }
private let value: CustomProtocol
static func ==(lhs: AnyEquatableCustomProtocol, rhs: AnyEquatableCustomProtocol) -> Bool {
return lhs.value.isEqualTo(rhs.value)
}
}
// instances typed as the protocol
let a: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let b: CustomProtocol = B(title: "First title", subtitle: "First subtitle")
let equalA: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let unequalA: CustomProtocol = A(title: "Second title", subtitle: "Second subtitle")
// equality tests
print(a.asEquatable() == b.asEquatable()) // prints false
print(a.asEquatable() == equalA.asEquatable()) // prints true
print(a.asEquatable() == unequalA.asEquatable()) // prints false
要注意的是,通过这种方法,实际的==
比较被委托给底层的具体类型,但我们只处理协议类型来维护抽象。
这里我只使用了类型擦除实例进行一次比较。但是,由于类型橡皮擦符合CustomProtocol
,因此可以保存这些实例并在预期协议类型的任何地方使用。因为它们符合Equatable
,所以它们也可以用于需要Equatable
一致性的任何地方。
仅针对上下文,本文解释了为什么不建议直接在协议上实现Equatable
一致性甚至==
函数:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/
因此类型擦除。
希望这会有所帮助。
答案 2 :(得分:0)
问题是rhs参数与lhs类型不同。它们只是遵循相同的协议。
您可以通过将通用类型用作第二个参数来解决该问题。
exension CustomProtocol {
static func ==<T: CustomProtocol>(lhs: Self, rhs: T) -> Bool {
return lhs.title == rhs.title
}
}