gpt4 book ai didi

swift - == 自定义类的重载并不总是被调用

转载 作者:IT王子 更新时间:2023-10-29 05:43:09 25 4
gpt4 key购买 nike

我有一个全局定义的自定义运算符,如下所示:

func ==(lhs: Item!, rhs: Item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}

如果我执行此代码:
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual是假的。在这种情况下,我确定我的自定义运算符没有触发。但是,如果我将此代码添加到操场中:
//same function
func ==(lhs: Item!, rhs: item!)->Bool {
return lhs?.dateCreated == rhs?.dateCreated
}

//same code
let i1 = Item()
let i2 = Item()
let date = Date()
i1.dateCreated = date
i2.dateCreated = date
let areEqual = i1 == i2
areEqual是真的——我假设在这种情况下我的自定义运算符被解雇了。

我没有定义其他自定义运算符会导致非操场情况下的冲突,以及 Item class 在这两种情况下都是相同的,那么为什么我的自定义运算符没有在操场外被调用?
Item类继承自 Object class provided by Realm ,最终继承自 NSObject .我还注意到,如果我为重载定义非可选输入,当输入是可选的时,它不会被触发。

最佳答案

你在这里尝试做的事情有两个主要问题。

1. 重载解决方案有利于父类(super class)型而不是可选的提升

您已声明您的 == Item! 的过载参数而不是 Item参数。通过这样做,类型检查器更倾向于静态调度到 NSObject == 的过载,因为看起来类型检查器更喜欢从子类到父类(super class)的转换而不是可选的提升(虽然我无法找到确认这一点的来源)。

通常,您不需要定义自己的重载来处理可选项。通过使给定类型符合 Equatable ,您将自动获得 an == overload它处理该类型的可选实例之间的相等性检查。

一个更简单的例子说明了父类(super class)重载优于可选的子类重载是:

// 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

如果 Bar?过载更改为 Bar – 将改为调用该重载。

因此,您应该更改您的过载以获取 Item参数代替。您现在可以使用该重载来比较两个 Item平等的例子。但是,由于下一个问题,这不会完全解决您的问题。

2.子类不能直接重新实现协议(protocol)要求
Item不直接符合 Equatable .相反,它继承自 NSObject ,已经符合 Equatable . ==的实现只是转发到 isEqual(_:) – 默认情况下比较内存地址(即检查两个实例是否完全相同)。

这意味着如果您重载 ==Item ,该重载无法动态分派(dispatch)到。这是因为 Item没有自己的协议(protocol)见证表以符合 Equatable – 相反,它依赖于 NSObject的 PWT,它将分派(dispatch)到它的 ==重载,只需调用 isEqual(_:) .

(协议(protocol)见证表是用于通过协议(protocol)实现动态调度的机制 - 有关更多信息,请参阅 this WWDC talk。)

因此,这将防止您的重载在通用上下文中被调用,包括前面提到的免费 ==选项的重载——解释为什么当您尝试比较时它不起作用 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

因此,即使您要更改重载以使其需要 Item输入,如果 ==曾经从 Item 上的通用上下文中调用例如,您的重载不会被调用。 NSObject的过载会。

这种行为有点不明显,并已作为错误提交 – SR-1729 .正如 Jordan Rose 所解释的,其背后的原因是:

[...] The subclass does not get to provide new members to satisfy the conformance. This is important because a protocol can be added to a base class in one module and a subclass created in another module.



这是有道理的,因为子类所在的模块必须重新编译才能使其满足一致性——这可能会导致有问题的行为。

然而值得注意的是,这个限制只对运算符(operator)要求有真正的问题,因为其他协议(protocol)要求通常可以被子类覆盖。在这种情况下,覆盖的实现被添加到子类的 vtable 中,允许动态调度按预期进行。但是,目前无法在不使用辅助方法(例如 isEqual(_:) )的情况下使用运算符实现此目的。

解决方案

因此,解决方案是覆盖 NSObject isEqual(_:) 方法和 hash属性而不是重载 == (请参阅 this Q&A 了解如何解决此问题)。这将确保您的相等实现将始终被调用,而不管上下文如何——因为您的覆盖将被添加到类的 vtable 中,从而允许动态调度。

覆盖背后的原因 hash以及 isEqual(_:)是您需要保持这样的 promise ,即如果两个对象比较相等,则它们的哈希值必须相同。如果 Item,则可能会发生各种奇怪的事情。曾经被散列过。

显然,非 NSObject的解决方案派生类将定义您自己的 isEqual(_:)方法,并让子类覆盖它(然后只有 == 重载链)。

关于swift - == 自定义类的重载并不总是被调用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/42283715/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com