我有一个类A
,它符合Equatable
协议并实现==
功能。在子类B
中,我使用更多检查覆盖==
。
但是,当我在B
的两个实例数组(两者都具有类型Array<A>
)之间进行比较时,会调用==
的{{1}}。当然,如果我将两个数组的类型更改为A
,则会调用Array<B>
的{{1}}。
我提出了以下解决方案:
==
看起来很丑陋,必须为B
的每个子类进行扩展。有没有办法确保首先调用A.swift:
internal func ==(lhs: A, rhs: A) -> Bool {
if lhs is B && rhs is B {
return lhs as! B == rhs as! B
}
return ...
}
子类?
答案 0 :(得分:39)
为包含A
的{{1}}调用Array<A>
的相等性的原因是自由函数的重载是静态解决的,而不是动态解析的 - 也就是说,在编译时在类型上,而不是在运行时基于指向的值。
这并不奇怪,因为B
未在类中声明,然后在子类中重写。这似乎是非常有限的,但老实说,使用传统的OO技术定义多态相等是非常(并且看似困难)的。有关详细信息,请参阅this link和this paper。
天真的解决方案可能是在==
中定义动态调度的函数,然后定义A
来调用它:
==
然后,当您实施class A: Equatable {
func equalTo(rhs: A) -> Bool {
// whatever equality means for two As
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.equalTo(rhs)
}
时,您将覆盖B
:
equalTo
你仍然需要进行一次class B: A {
override func equalTo(rhs: A) -> Bool {
return (rhs as? B).map { b in
return // whatever it means for two Bs to be equal
} ?? false // false, assuming a B and an A can’t be Equal
}
}
舞蹈,因为你需要确定右手参数是as?
(如果B
直接接受了equalTo
,它不会是合法的覆盖。)
这里还隐藏着一些可能令人惊讶的行为:
B
也就是说,参数的顺序会改变行为。这不好 - 人们希望平等是对称的。所以你真的需要上面链接中描述的一些技术才能正确解决这个问题。
此时你可能觉得这一切都变得有点不必要了。它可能是,特别是在Swift标准库的let x: [A] = [B()]
let y: [A] = [A()]
// this runs B’s equalTo
x == y
// this runs A’s equalTo
y == x
文档中给出以下注释:
平等意味着可替代性。
Equatable
,x == y
和x
时 可以在任何仅依赖于它们的值的代码中互换。由三等于
y
区分的类实例标识是 特别是不是实例值的一部分。揭露其他非价值 不建议===
类型的方面, 的任何方面 暴露应该在文档中明确指出。
鉴于此,您可能会非常想重新考虑使用Equatable
实施,如果您实现平等的方式是而不是,那么您对此感到满意两个相等的值相互替换。避免这种情况的一种方法是将对象标识视为衡量平等的标准,并用Equatable
来实现==
,只需要为超类做一次。或者,您可以问自己,真的是否需要实现继承?如果没有,请考虑抛弃它并使用值类型,然后使用协议和泛型来捕获您正在寻找的多态行为。
答案 1 :(得分:0)
我遇到了类似的问题,因为我想在MKPointAnnotation子类(继承自NSObject)上使用difference(from:)
。即使我将func ==
添加到注释子类中,difference(from:
仍将调用NSObject的==
实现。我相信这只是比较2个对象的内存位置,这不是我想要的。为了使difference(from:
正常工作,我必须为我的注释子类实现override func isEqual(_ object: Any?) -> Bool {
。在正文中,我将确保object
与子类的类型相同,然后在其中进行比较。