Swift Equality

Here at Teletronics, we do (amongst many other things) mobile app development. For native iOS development, we mainly use Swift. With Swift’s mix of functional features in an object-oriented paradigm, it can be difficult to understand how even the default equality behaviour works, let alone overriding it with your own.

This blog post will try to shed some light on both.

50 Shades of Equality

Ok, so there are actually (only) 3 shades of equality:

The === operator

This operator tests if the references of two instances are the same (in other words; are identical pointers).

The == operator

This operator is defined by the Equatable protocol.

All basic types such as Int, Float, String and NSObject implements the Equatable protocol.

If your custom class (or struct) needs to work with the == operator, then you need to implement the Equatable protocol. Classes can also choose to extend NSObject, whose default implementation of the == operator use the .isEqual() function (see paragraph below).

The Equatable protocol is implemented like this:

Note: Operator overload is done in global scope (even though the Equatable protocol defines the == operator in local scope). The reason for defining operator overloads in global space, is due to the fact that the right side could be a different type than MyClass, e.g. Int or MyOtherClassInADifferentFile, and then in which type declaration should the operator be defined?

The .isEqual() function

This function is defined in NSObjectProtocol which is implemented by NSObject. The default implementation of .isEqual(), as written in the documentation“simply checks for pointer equality”.

Note: This “pointer equality” check is NOT the same as ===. So overloading the === operator will not change the .isEqual() functionality.

Overriding Equality

So, how do you add your own equality implementation for each of the above equality operators and functions?

The === operator

I have no idea why anyone would overload the === operator, but it is done as with any other operator overload:

The above types for left and right can be two different types and the implementation can be your own custom implementation.

Note: As mentioned above, the function is declared in global scope, so it could be in any swift file. However, it would be appropriate to declare the function in a file that is related to either the type of the left or right parameters.

The == operator

Overloading the == operator is much more common, and makes good sense for both custom classes and structs.

The overload is done exactly like any other operator overload:

Note: There is no function to overload for the != operator. The != operator simply uses the == operator and then negates the result.

The .isEqual() function

As stated previously, the .isEqual() function is defined in NSObjectProtocol which is implemented by NSObject. So in order to override the .isEqual() function, your class needs to either implement NSObjectProtocol or, which is the more common case, extend the NSObject class:

Since the .isEqual() function takes an object of optional AnyObject type, we need to first check that the object can be unwrapped (using if let) and cast to MyClass (using as?). Afterwards, we can implement the actual equality check using the id variable.

Note: When overriding the .isEqual() function, it is very important to also override the hash value of the class. This is discussed in more detail in the following paragraph.

Dictionary and Set

When using Dictionary and Set with objects of a type that has overriden the default .isEqual() function, it is important to also override the hash value for that object. It is important that two equal objects also have equal hash values. The reason for this, is that Dictionary and Set use both the equality and hash values in order to add and get elements.

Overriding Hash Value and Equality

Now, it turns out that overriding the hash value and equality, so that your custom type can be used in Dictionary and Set, is not as straight-forward as one might think.

Let’s investigate a little:

If one peers into the NSObject, it comes apparent that the NSObject has two hash values defined: The hash property (defined by NSObjectProtocol) and the hashValue property (defined by Hashable).

And it has three ways to test equality: The .isEqual() function (defined by NSObjectProtocol), the == operator (defined by Equatable) and the === operator (defined globally).

So which does the Dictionary and Set use? Which should you override?

The answer is: It depends…

Depends on what?

It depends on whether your class extends NSObject or just implement Hashable. Dictionary and Set have a generic constraint stating that elements should always at least implement Hashable (which NSObject does).

If the class just implements Hashable, then the hashValue property and the == property are used when adding and getting elements in Dictionary and Set, so you’ll need to override those.

If the class extend NSObject, then the hashValue property and .isEqual() function are used, so you’ll need to override those.

However! The Hashable protocol is a Swift protocol, so when your class extends NSObject and is used in an ObjC context (for instance by the iOS Foundation), then Hashable’s hashValue isn’t used, but instead the hash property is used.

Confusing! How do I get this right?

Yes, it is confusing, but if you follow the below example, then I believe you and your class are well suited as elements in Dictionary and Set:

If your class only implements Hashable, you can get rid of the hash property override and the .isEqual() function override.

What if my equality implementation use more than one variable?

Well, then your hash implementation must use the same variables to calculate the hash value. A simple approach is to xor the hash values of the variables used. That approach is fine for Dictionary and Set with a small amount of elements (a “guestimate” would be less than 1000), but for larger amounts, there would be too many hash value collisions and performance would suffer. Discussing hashing functions is beyond the scope of this article, but I’m sure StackOverflow and Google has some suggestions.

Now hiring in Dubai! - Read more
July 3rd, 2016 by