如何为基于两个实现该协议的实例的身份的协议实现Equatable协议?

5

我正在尝试为基于左操作数和右操作数的身份的协议实现等同协议。 换句话说:如何为决定两个实现此协议的实例(在我的情况下是 iNetworkSubscriber)是否相同(具有相同的对象引用)的协议实现 Equatable 协议。错误消息包含在以下代码中:

protocol iNetworkSubscriber : Equatable {

    func onMessage(_ packet: NetworkPacket)

}

func ==(lhs: iNetworkSubscriber, rhs: iNetworkSubscriber) -> Bool {     // <- Protocol 'iNetworkSubscriber' can only be used as a generic constraint because it has Self or associated type requirements
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)               // <- Cannot invoke initializer for type 'ObjectIdentifier' with an argument list of type '(iNetworkSubscriber)'
}

我也尝试使用身份运算符本身:

func ==(lhs: iNetworkSubscriber, rhs: iNetworkSubscriber) -> Bool {     // <- Protocol 'iNetworkSubscriber' can only be used as a generic constraint because it has Self or associated type requirements
    return lhs === rhs                                                  // <- Binary operator '===' cannot be applied to two 'iNetworkSubscriber' operands
}

有人有解决这个问题的想法吗?


3
可能是Swift相等性需要ObjectIdentifier吗?的重复问题。 - Max Smolens
很不幸...另一个线程中讨论的是classesObjectIdentifier,而我需要它适用于protocol。我的目标是确定实现iNetworkSubscriber协议的两个实例是否是相同的实例。有什么想法如何实现这一点吗? - salocinx
2个回答

3

这里有两个问题。第一个问题是你不能在值类型上使用ObjectIdentifier。所以你必须声明此协议要求引用(类)类型:

protocol NetworkSubscriber : class, Equatable {
    func onMessage(_ packet: NetworkPacket)
}

请不要在协议的开头添加小写字母i。这在Swift中有几个方面会造成混淆。
此外,您不能将此协议用作类型。它描述了一种类型(因为它通过Equatable依赖于Self)。因此,接受它的函数必须是泛型的。
func ==<T: NetworkSubscriber>(lhs: T, rhs: T) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

鉴于 NetworkSubscriber 必须是一个类,你应该非常谨慎地考虑是否应该在这里使用继承而不是协议。带有关联类型的协议非常复杂,混合使用类和协议会更加复杂。如果你已经在使用类,则类继承要简单得多。


你能否做一个关于带有关联类型的协议的演讲?我认为你可以很好地为我们解释这个问题 :D - Alexander
感谢Rob提供如此全面的答案!我发现我需要重新打开Swift教材,更深入地学习协议部分。我试图采用我习惯使用的C#/Java模式。因此,我还在协议(接口)前加了前缀“i”。但是我现在意识到Swift协议!= C#/Java接口;-) 我将采用继承方法。 - salocinx
我之前做过一个关于协议的演讲。尽管它仍然相当相关,但我可能会说一些不同的话,特别是关于将人们推回到类中;在我们今天拥有的Swift中,它们的效果要好得多。https://www.youtube.com/watch?v=QCxkaTj7QJs - Rob Napier
关于Swift协议与C#接口不同的说法是非常正确的。特别是,你不应该仅仅为了让你的代码变成通用的而使用协议,更不要使用带有关联类型的协议。PAT是复杂而强大的工具,在stdlib中很有意义,但我发现在应用级别的代码中很少有意义。每当我看到一个关于PAT的SO问题时,我都会问:“你真的需要在这里使用PAT吗?还是你只是因为认为你应该使用‘通用’?”大多数情况下,事实证明它并不是实际程序所必需的。 - Rob Napier

2

仅针对对象(类的实例,class协议)才有意义的身份比较。因此,您立即知道您需要在协议上添加一个class约束:

protocol NetworkSubscriber: class, Equatable {
    func onMessage(_ packet: NetworkPacket)
}

完成此操作后,身份比较运算符===将可用于NetworkSubscriber的实例(因为现在它们被保证是对象)。我建议您直接使用===,而不是定义一个调用=====,以明确表明您正在执行身份比较,而不是值比较。

let ns1 = getNewNetworkSubscriber()
let ns2 = getNewNetworkSubscriber()
print(n1 === n2) // false
print(n1 === n1) // true

感谢您提供的进一步解释。 - salocinx
@salocinx 而且我忘了提到:如果你需要 Equatable 一致性,你仍然可以实现 ==,只要可能就使其基于值。在 === 中的 == 有点不可靠。 - Alexander

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接