Swift Equatable Protocol

How to implement Equatable protocol for a protocol based on the identity of two instances that implement this protocol?

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.

Equatable protocol in swift

First note that your implementation of == can be simplified to

static func ==(_ lhs: Game, _ rhs:Game) -> Bool {
switch (lhs.correctAnswer, rhs.correctAnswer) {
case (let lhsInt as Int, let rhsInt as Int):
if lhsInt != rhsInt {
return false
}
case (let lhsString as String, let rhsString as String):
if lhsString != rhsString {
return false
}
default:
return false
}
return lhs.description == rhs.description &&
lhs.name == rhs.name &&
lhs.points == rhs.points
}

so that adding another answer type just means adding one additional
case.

The problem is that the compiler cannot verify that all possible
answer types are handled in your == function, so this approach
is error-prone.

What I actually would do is to use an enum Answer instead of a
protocol, and make that Equatable:

enum Answer: Equatable {
case int(Int)
case string(String)
}

Note that you don't have to implement ==. As of Swift 4.1, the
compiler synthesizes that automatically, see
SE-0185 Synthesizing Equatable and Hashable conformance.

And now Game simplifies to

struct Game: GameDescriber, Equatable, CorrectAnswer {
var correctAnswer: Answer
var name: String
var description: String
var points: Int
}

where the compiler synthesizes == as well, with a default implementation that compares all stored properties for equality.

Adding another answer type is simply done by adding another case to
the enumeration:

enum Answer: Equatable {
case int(Int)
case string(String)
case intArray([Int])
}

without any additional code.

Swift Equatable Protocol

Move this function

func == (lhs: Cookie, rhs: Cookie) -> Bool {
return lhs.column == rhs.column && lhs.row == rhs.row
}

Outside of the cookie class. It makes sense this way since it's overriding the == operator at the global scope when it is used on two Cookies.

How to properly implement the Equatable protocol in a class hierarchy?

After lots of research and some trial and error I finally came up with a working solution. The first step was moving the == operator from inside the class to the global scope. This fixed the errors about static and final.

For the base class this became:

func == (lhs: Base, rhs: Base) -> Bool {
return lhs.x == rhs.x
}

class Base : Equatable {
var x : Int
}

And for the subclass:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
return true
}

class Subclass : Base {
var y : String
}

Now the only part left is figuring out how to call the == operator of the base class from the == operator of the subclass. This led me to the final solution:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
if lhs.y == rhs.y {
if lhs as Base == rhs as Base {
return true
}
}

return false
}

That first if statement results in a call to the == operator in the base class.


The final solution:

Base.swift:

func == (lhs: Base, rhs: Base) -> Bool {
return lhs.x == rhs.x
}

class Base : Equatable {
var x : Int
}

Subclass.swift:

func == (lhs: Subclass, rhs: Subclass) -> Bool {
if lhs.y == rhs.y {
if lhs as Base == rhs as Base {
return true
}
}

return false
}

class Subclass : Base {
var y : String
}

Protocol conforming to Equatable for Diffing

You don't have to conform to Equatable in order to be able to use the == operator. You can just define an operator like that yourself, without conforming to Equatable.

For convenience's sake, I'll assume that TextMessage and VideoMessage already conforms to Equatable.

First, write a method that compares Messages:

func messageEqual(m1: Message, m2: Message) -> Bool {
if let textMessage1 = m1 as? TextMessage, let textMessage2 = m2 as? TextMessage {
return textMessage1 == textMessage2
}
if let videoMessage1 = m1 as? VideoMessage, let videoMessage2 = m2 as? VideoMessage {
return videoMessage1 == videoMessage2
}
return false
}

Then a the == operator for [Message]:

func ==(messages1: [Message], messages2: [Message]) -> Bool {
return messages1.count == messages2.count &&
zip(messages1, messages2).allSatisfy(messageEqual)
}

Now l == r should compile.

How to make array of protocol at the same time conform to Equatable?

Thanks to @Alexander and his pointed video resource - https://youtu.be/_m6DxTEisR8?t=2585

Here's is the good workaround, to overcome the current limitation of Swift's protocol.

protocol Animal {
func isEqual(to: Animal) -> Bool
}

func isEqual(lhs: [Animal], rhs: [Animal]) -> Bool {
let count0 = lhs.count
let count1 = rhs.count

if count0 != count1 {
return false
}

for i in 0..<count0 {
if !(lhs[i].isEqual(to: rhs[i])) {
return false
}
}

return true
}

// struct. By conforming Equatable, struct is getting an auto
// implementation of "static func == (lhs: Dog, rhs: Dog) -> Bool"
struct Dog: Animal, Equatable {
func isEqual(to other: Animal) -> Bool {
guard let other = other as? Dog else { return false }
return self == other
}
}

// class
class Cat: Animal, Equatable {
static func == (lhs: Cat, rhs: Cat) -> Bool {
// TODO:
return true
}

func isEqual(to other: Animal) -> Bool {
guard let other = other as? Cat else { return false }
return self == other
}
}

var animals0 = [Animal]()
var animals1 = [Animal]()

// Instead of using
// if animals0 == animals1 {
// we will use the following function call

if isEqual(lhs: animals0, rhs: animals1) {

}


Related Topics



Leave a reply



Submit