5

I am trying to implement the Equatable protocol for a protocol based on the left' and right's operand identity. I other words: How do I implement the Equatable protocol for a protocol to determine if two instances that implement this protocol (in my case iNetworkSubscriber) are identical (same object reference). Like that (error message is include in the code below):

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)'
}

... and I also tried it with the identity operator itself:

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
}

Somebody an idea how to solve this issue?

salocinx
  • 3,715
  • 8
  • 61
  • 110
  • 3
    Possible duplicate of [ObjectIdentifier needed for Swift equality?](https://stackoverflow.com/questions/44215559/objectidentifier-needed-for-swift-equality) – Max Smolens Jan 18 '18 at 15:14
  • Unfortunately not... `ObjectIdentifier` in the other thread is discussed for `classes`, I need it for a `protocol`. My goal is to determine if two instances that implement the `iNetworkSubscriber` protocol are in fact identical instances. Any ideas how to achieve this? – salocinx Jan 18 '18 at 15:23

2 Answers2

3

There are two problems here. The first is that you can't use ObjectIdentifier on value types. So you must declare this protocol to require reference (class) types:

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

(Please do not add a lowercase i to the beginning of protocols. This is confusing in several ways in Swift.)

Then, you cannot use this protocol as a type. It describes a type (because it relies on Self via Equatable). So functions that accept it must be generic.

func ==<T: NetworkSubscriber>(lhs: T, rhs: T) -> Bool {
    return ObjectIdentifier(lhs) == ObjectIdentifier(rhs)
}

Given that NetworkSubscriber must be a class, you should ask very carefully whether you should be using inheritance here rather than a protocol. Protocols with associated types are very complex to use, and mixing classes and protocols creates even more complexity. Class inheritance is much simpler if you're already using classes.

Alexander
  • 59,041
  • 12
  • 98
  • 151
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Can you please do a talk on protocols with associated types? I think you could do a really good job on breaking that down for us :D – Alexander Jan 18 '18 at 15:31
  • Thanks Rob for the very comprehensive answer! I see that I need to re-open the Swift text book and study the protocol section more deeply. I tried to adopt patterns from C#/Java that I am used to use. Therefore I also prefixed the protcol (interface) with ´i´. But I now realised that Swift protocols != C#/Java interfaces ;-) I will go for the inheritance approach. – salocinx Jan 18 '18 at 15:39
  • I did a talk a while back on protocols. It's still fairly relevant, though I'd probably say different things today, especially about pushing people back to classes; they just work much better in the Swift we have today. https://www.youtube.com/watch?v=QCxkaTj7QJs – Rob Napier Jan 18 '18 at 18:18
  • And very true about Swift protocols not being the same as C# interfaces. In particular, you should not reach for protocols, and definitely not protocols with associated types, just to make your code generic. PATs are complex and powerful tools that make sense in stdlib, but I've found only rarely make sense in app-level code. Every time I see an SO question about PATs, I ask "do you really need PATs here, or are you just trying to 'generic' because you think you should be?" Most of the time it turns out that it wasn't necessary for the actual program at hand. – Rob Napier Jan 18 '18 at 18:23
2

Identity comparison only makes sense for objects (instances of classes, class protocols). So right away, you know that you need a class constraint on your protocol:

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

Once you do this, the identity comparison operator === becomes available for instances of NetworkSubscriber (because they're now guaranteed to be objects). Rather than defining an == that calls ===, I would recommend you use === directly, to make it explicit that you're performing identity comparison, not value comparison:

let ns1 = getNewNetworkSubscriber()
let ns2 = getNewNetworkSubscriber()
print(n1 === n2) // false
print(n1 === n1) // true
Alexander
  • 59,041
  • 12
  • 98
  • 151
  • Thank you for the further explanations. – salocinx Jan 18 '18 at 15:47
  • @salocinx And I forgot to mention: If you need `Equatable` conformance, you can still implement `==`, I would just make that value-based if possible. `==` within `===` is kinda sketchy – Alexander Jan 18 '18 at 15:49