您在这里尝试做的事情有两个主要问题。
1. 重载解决方案有利于超类型而不是可选的升级
你已经宣布了你的==
过载Item!
参数而不是Item
参数。通过这样做,类型检查器更倾向于静态分派到NSObject
的过载为==
,因为看起来类型检查器更喜欢子类到超类的转换而不是可选的升级(尽管我无法找到来源来确认这一点)。
通常,您不需要定义自己的重载来处理选项。通过使给定类型符合Equatable
,你会自动得到an ==超载它处理该类型的可选实例之间的相等性检查。
演示超类重载优于可选子类重载的一个更简单的示例是:
// custom operator just for testing.
infix operator <===>
class Foo {}
class Bar : Foo {}
func <===>(lhs: Foo, rhs: Foo) {
print("Foo's overload")
}
func <===>(lhs: Bar?, rhs: Bar?) {
print("Bar's overload")
}
let b = Bar()
b <===> b // Foo's overload
If the Bar?
过载改为Bar
– 将会调用该重载。
因此你应该改变你的超负荷以采取Item
参数代替。您现在可以使用该重载来比较两个Item
平等的实例。然而,这不会fully解决您因下一个问题而产生的问题。
2.子类不能直接重新实现协议需求
Item
不directly符合Equatable
。相反,它继承自NSObject
,已经符合Equatable
。其实施==
只是转发到isEqual(_:)– 默认情况下比较内存地址(即检查两个实例是否是完全一样实例)。
这意味着如果你超载==
for Item
,该过载是not能够动态调度到。这是因为Item
没有自己的协议见证表来符合Equatable
– 相反,它依赖于NSObject
的 PWT,将发送至its ==
重载,只需调用isEqual(_:)
.
(Protocol witness tables are the mechanism used in order to achieve dynamic dispatch with protocols – see this WWDC talk on them for more info.)
因此,这将防止您的重载在通用上下文中被调用,包括上述免费的==
选项的重载 - 解释为什么当你尝试比较时它不起作用Item?
实例。
此行为可以在以下示例中看到:
class Foo : Equatable {}
class Bar : Foo {}
func ==(lhs: Foo, rhs: Foo) -> Bool { // gets added to Foo's protocol witness table.
print("Foo's overload") // for conformance to Equatable.
return true
}
func ==(lhs: Bar, rhs: Bar) -> Bool { // Bar doesn't have a PWT for conformance to
print("Bar's overload") // Equatable (as Foo already has), so cannot
return true // dynamically dispatch to this overload.
}
func areEqual<T : Equatable>(lhs: T, rhs: T) -> Bool {
return lhs == rhs // dynamically dispatched via the protocol witness table.
}
let b = Bar()
areEqual(lhs: b, rhs: b) // Foo's overload
所以,即使你were改变你的超载,这样就需要Item
输入,如果==
曾经从通用上下文中调用过Item
例如,您的重载不会被调用。NSObject
的超载将会。
这种行为有些不明显,已被归档为错误 –SR-1729。正如乔丹·罗斯所解释的,其背后的原因是:
[...] 子类无法提供新成员来满足一致性。这很重要,因为协议可以添加到一个模块中的基类和另一个模块中创建的子类中。
这是有道理的,因为子类所在的模块必须重新编译才能满足一致性——这可能会导致有问题的行为。
然而值得注意的是,这种限制只会对运营商要求产生真正的问题,因为其他协议要求通常可以是被覆盖通过子类。在这种情况下,覆盖的实现将添加到子类的 vtable 中,从而允许按预期进行动态分派。但是,目前如果不使用辅助方法(例如isEqual(_:)
).
解决方案
因此解决方案是override NSObject
's isEqual(_:)方法和hash
属性而不是重载==
(see 本次问答了解如何进行此操作)。这将确保您的相等实现将始终被调用,无论上下文如何 - 因为您的覆盖将被添加到类的 vtable 中,从而允许动态分派。
覆盖背后的原因hash
也isEqual(_:)
是你需要维持这样的承诺:如果两个对象比较相等,则它们的哈希值must是相同的。否则,可能会发生各种奇怪的情况,如果Item
曾经被散列过。
显然,对于非NSObject
派生类将定义您的own isEqual(_:)
方法,并让子类重写它(然后只需==
过载链)。