NSHipster Obscure Topics in Cocoa and Objective C

Equality

Equality & Identity

  1. In code, an object's identity is tied to its memory address.
  2. Two objects may be equal or equivalent to one another, if they share a common set of properties. Yet, those two objects may still be thought to be distinct, each with their own identity.

isEqualTo__:

NSAttributedString -isEqualToAttributedString:
NSData -isEqualToData:
NSData -isEqualToData:
NSDictionary -isEqualToDictionary:
NSHashTable -isEqualToHashTable:
NSIndexSet -isEqualToIndexSet:
NSNumber -isEqualToNumber:
NSOrderedSet -isEqualToOrderedSet:
NSSet -isEqualToSet:
NSString -isEqualToString:
NSTimeZone -isEqualToTimeZone:
NSValue -isEqualToValue:

The Curious Case of NSString Equality

NSString *a = @"Hello";
NSString *b = @"Hello";
BOOL wtf = (a == b); // YES (!)

NSString *a = @"Hello";
NSString *b = [NSString stringWithFormat:@"%@", @"Hello"];
BOOL wtf = (a == b); // NO (!)

To be perfectly clear: the correct way to compare two NSString objects is -isEqualToString:. Under no circumstances should NSString objects be compared with the == operator.

So what's going on here? Why does this work, when the same code for NSArray or NSDictionary literals wouldn't do this?

It all has to do with an optimization technique known as string interning, whereby one copy of immutable string values. NSString a and b point to the same copy of the interned string value @"Hello".

Again, this only works for statically-defined, immutable strings. Constructing identical strings with NSString +stringWithFormat: will objects with different pointers.

Interestingly enough, Objective-C selector names are also stored as interned strings in a shared string pool.

Hashing Fundamentals

  1. Object equality is commutative

    ([a isEqual:b] ⇒ [b isEqual:a])

  2. If objects are equal, their hash values must also be equal

    ([a isEqual:b] ⇒ [a hash] == [b hash])

  3. However, the converse does not hold: two objects need not be equal in order for their hash values to be equal

    ([a hash] == [b hash] ¬⇒ [a isEqual:b])

Implementing -isEqual: and hash in a Subclass