Airo Global Software

Think Beyond Future !

Equatable, Comparable, Identifiable, and Hashable solutions

Protocols are not new to iOS or its cousin OS X; in fact, the delegate protocol is the bread and butter of more than half of the frameworks, though this may change in the coming years with the introduction of async/await. Having said that, since SwiftUI's release in 2019, protocols appear to be changing their colors in the meantime.

That is because SwiftUI includes a number of mandatory protocols that are linked to the language itself. Although it is not always clear what is going on, basic protocols such as Equatable, Comparable, Identifiable, and Hashable are used.

Identifiable

This is the first protocol you'll likely encounter as a new SwiftUI coder when attempting to define a ForEach loop, for example, within a List — assuming we have an array of dice containing a custom struct.

struct ContentView: View {
 @State var dice = [Dice]()
 var body: some View {
   ForEach(dice) {
     Text(String($0.value))
   }
 }
}

The compiler is looking for a way to uniquely identify each row within your struct's loop. The dice variable shown here must be Identifiable. Conformance is obtained through the use of code such as this.

struct Dice: Identifiable {
 //  var id = UUID()
 var id = Date().timeIntervalSince1970 // epoch [dies Jan 19, 2038]
 var value: Int!
}

Hashable

The second protocol you're likely to encounter is Hashable, which SwiftUI requires for loops like the one shown here.

ForEach(dice, id: \.self) { die in
 Text("Die: \(die.value)")
}

But be careful, because including a third protocol Equatable with a definition shown will cause your code to crash.

struct Dice: Equatable, Hashable {
 var id = UUID()
 var value: Int!
 static func ==(lhs: Dice, rhs: Dice) -> Bool {
   lhs.value == rhs.value
 }
}

The hashable needs here necessitate the use of a unique identifier, similar to the identifiable protocol.

To use both the Hashable and the Equatable protocols, you must instruct the Hashable protocol to focus on the id, which is, of course, that unique Identifiable value.

extension Dice: Hashable {
 static func ==(lhs: Dice, rhs: Dice) -> Bool {
   lhs.id == rhs.id
 }
 func hash(into hasher: inout Hasher) {
   hasher.combine(id)
 }
}

However, the hash function can also be useful in this case because it guarantees that it will produce the same output given the same input. Although this example may appear to be a little pointless, you can use code like this to generate the same key repeatedly.

.onAppear {
 var hash = Hasher()
 hash.combine(die.id)
 print("hash \(hash.finalize()) \(die.hashValue)")
}

The main page at Apple provides a more real-world example of how this protocol can be used.

Comparable

Comparable, which appears to be nearly identical to Equatable, is the next protocol on my shortlist.

extension Dice: Comparable {
 static func < (lhs: Dice, rhs: Dice) -> Bool {
   lhs.value < rhs.value
 }
}

This code was added to our SwiftUI interface to enable us to use the new protocol/property.

if dice.count == 2 {
 if dice.first! > dice.last! {
   Text("Winner 1st")
 } else {
   Text("Winner 2nd")
 }
}

However, there is a catch. I can't use the == in the same way because I had to point to the id to conform to the Hashable protocol.

if dice.first! == dice.last! {
 Text("Unequal \(dice.hashValue)")
} else {
 Text("Equal \(dice.hashValue)")
}

To get around/fix this, I'll need to hire a new operator. The fix for the above necessitates the creation of a new infix operator, such as ==== Obviously, I'd need to change the code snippet above to use the ==== instead of the == shown.

infix operator ==== : DefaultPrecedence
extension Dice {
 static func ====(lhs: Dice, rhs: Dice) -> Bool {
   lhs.face == rhs.face
 }
}

I'm sure Apple would prefer that you use protocols in your everyday code to make it clear what you're trying to accomplish, essentially an extension of types that you can use on your custom objects.

Equatable

Okay, I admit that the infix operator isn't for everyone, especially Swift purists. So here's an alternative that's more equitable and doesn't require an infix. Within it, I define the view as adhering to the equatable protocol in order to target the face of my die.

Please take note of what I used.

onAppear to initialize die1 and die2, and then.onChange to handle all subsequent reloads of the dice whenever I rolled a new pair.

struct EqualView: View, Equatable {

 static func == (lhs: EqualView, rhs: EqualView) -> Bool {
   lhs.die1?.face == rhs.die2?.face
 }
 @State var die1:Dice? = nil
 @State var die2:Dice? = nil
 @Binding var dice:[Dice]
 var body: some View {
   Color.clear
     .frame(width: 0, height: 0, alignment: .center)
     .onAppear {
       die1 = dice.first!
       die2 = dice.last!
     }
     .onChange(of: dice) { values in
       die1 = dice.first!
       die2 = dice.last!
     }
   if die1?.face == die2?.face {
     Text("Equal ")
   } else {
     Text("Unequal ")
   }
 }
}

This is all about the swift protocols that are commonly used, hope you all understand this topic. If you have any doubt about the Swift protocols used in Swift UI. Don’t hesitate to contact us. Airo Global Software will be your digital partner.

E-mail id: [email protected]

enter image description here

Author - Johnson Augustine
Chief Technical Director and Programmer
Founder: Airo Global Software Inc
LinkedIn Profile: www.linkedin.com/in/johnsontaugustine/