== 自定义类的重载并不总是被调用 [英] == overload for custom class is not always called

查看:30
本文介绍了== 自定义类的重载并不总是被调用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I have a custom operator defined globally like so:

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 为假.在这种情况下,我确定我的自定义运算符没有触发.但是,如果我将此代码添加到操场中:

areEqual is false. In this case I know for sure that my custom operator is not firing. However, if I add this code into the playground:

//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 为真——我假设我的自定义操作符在这种情况下被触发.

areEqual is true -- I'm assuming my custom operator is fired in this case.

我没有定义其他自定义运算符会导致非操场情况下的冲突,并且 Item 类在两种情况下都是相同的,那么为什么我的自定义运算符没有被外部调用游乐场?

I have no other custom operators defined that would cause a conflict in the non-playground case, and the Item class is the same in both cases, so why is my custom operator not being called outside the playground?

Item 类继承自 提供的 code>Object 类,最终继承自 NSObject.我还注意到,如果我为重载定义了非可选输入,当输入是可选的时,它不会被触发.

The Item class inherits from the Object class provided by Realm, which eventually inherits from NSObject. I also noticed that if I define non-optional inputs for the overload, when the inputs are optionals it's not fired.

推荐答案

您在此处尝试执行的操作存在两个主要问题.

There are two main problems with what you're trying to do here.

您已经为Item! 参数而不是Item 参数声明了== 重载.通过这样做,类型检查器更倾向于静态分派到 NSObject== 的重载,因为类型检查器似乎支持子类到超类的转换可选的促销活动(不过我一直无法找到确认这一点的来源).

You've declared your == overload for Item! parameters rather than Item parameters. By doing so, the type checker is weighing more in favour of statically dispatching to NSObject's overload for ==, as it appears that the type checker favours subclass to superclass conversions over optional promotion (I haven't been able to find a source to confirm this though).

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

Usually, you shouldn't need to define your own overload to handle optionals. By conforming a given type to Equatable, you'll automatically get an == overload which handles equality checking between optional instances of that type.

一个更简单的例子是:

// 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 - 将改为调用该重载.

If the Bar? overload is changed to Bar – that overload will be called instead.

因此,您应该将重载改为采用 Item 参数.您现在可以使用该重载来比较两个 Item 实例的相等性.但是,由于下一个问题,这不会完全解决您的问题.

Therefore you should change your overload to take Item parameters instead. You'll now be able to use that overload to compare two Item instances for equality. However, this won't fully solve your problem due to the next issue.

Item 并不直接符合 Equatable.相反,它继承自 NSObject,后者已经符合 Equatable.它的 == 实现只是转发到 isEqual(_:) - 默认情况下比较内存地址(即检查两个实例是否完全相同实例).

Item doesn't directly conform to Equatable. Instead, it inherits from NSObject, which already conforms to Equatable. Its implementation of == just forwards onto isEqual(_:) – which by default compares memory addresses (i.e checks to see if the two instances are the exact same instance).

这意味着如果您为 Item 重载 ==,则该重载不能被动态分派到.这是因为 Item 没有获得自己的协议见证表以符合 Equatable - 它而是依赖于 NSObject 的 PWT,它将分派到它的 == 重载,只需调用 isEqual(_:).

What this means is that if you overload == for Item, that overload is not able to be dynamically dispatched to. This is because Item doesn't get its own protocol witness table for conformance to Equatable – it instead relies on NSObject's PWT, which will dispatch to its == overload, simply invoking isEqual(_:).

(协议见证表是用于通过协议实现动态调度的机制 - 参见 这个 WWDC 谈话关于他们的更多信息.)

(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? 实例.

This will therefore prevent your overload from being called in generic contexts, including the aforementioned free == overload for optionals – explaining why it doesn't work when you attempt to compare Item? instances.

可以在以下示例中看到此行为:

This behaviour can be seen in the following example:

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 的重载将.

So, even if you were to change your overload such that it takes an Item input, if == was ever called from a generic context on an Item instance, your overload won't get called. NSObject's overload will.

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

This behaviour is somewhat non-obvious, and has been filed as a bug – SR-1729. The reasoning behind it, as explained by Jordan Rose is:

[...] 子类不能提供新成员来满足一致性.这很重要,因为可以将协议添加到一个模块中的基类和另一个模块中创建的子类.

[...] 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.

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

Which makes sense, as the module in which the subclass resides would have to be recompiled in order to allow it to satisfy the conformance – which would likely result in problematic behaviour.

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

It's worth noting however that this limitation is only really problematic with operator requirements, as other protocol requirements can usually be overridden by subclasses. In such cases, the overriding implementations are added to the subclass' vtable, allowing for dynamic dispatch to take place as expected. However, it's currently not possible to achieve this with operators without the use of a helper method (such as isEqual(_:)).

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

The solution therefore is to override NSObject's isEqual(_:) method and hash property rather than overloading == (see this Q&A for how to go about this). This will ensure that your equality implementation will always be called, regardless of the context – as your override will be added to the class' vtable, allowing for dynamic dispatch.

覆盖 hashisEqual(_:) 背后的原因是你需要保证如果两个对象比较相等,它们的哈希值 必须相同.如果 Item 曾经被散列过,那么各种奇怪的事情都可能发生.

The reasoning behind overriding hash as well as isEqual(_:) is that you need to maintain the promise that if two objects compare equal, their hashes must be the same. All sorts of weirdness can occur otherwise, if an Item is ever hashed.

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

Obviously, the solution for non-NSObject derived classes would be to define your own isEqual(_:) method, and have subclasses override it (and then just have the == overload chain to it).

这篇关于== 自定义类的重载并不总是被调用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆