Equality & Identity

Two objects may be equal or equivalent to one another, if they share a common set of observable properties. Yet, those two objects may still be thought to be distinct, each with their own identity.

NSObject tests equality with another object with the method isEqual:, like so:

@implementation NSObject (Approximate)
- (BOOL)isEqual:(id)object {
    return self == object;
}
@end

For container class like NSArray, NSDictionary, and NSString, a deep equality comparison is require to determine that each member in the collection is equal.

Subclasses of NSObject implementing their own isEqual: method are expected to do the following:

  • Implement a new isEqualTo__ClassName__: method, which performs the meaningful value comparison.
  • Override isEqual: to make class and object identity checks, falling back on the aforementioned value comparison method.
  • Override hash.

An educated assumption of how NSArray might do this:

@implementation NSArray (Approximate)
- (BOOL)isEqualToArray:(NSArray *)array {
    if (!array || [self count] != [array count]) {
        return NO;
    }

    for (NSUInteger idx = 0 ; idx < [array count]; idx++) {
        if (![self[idx] isEqual:array[idx]]) {
            return NO;
        }
    }

    return YES;
}

- (BOOL)isEqual:(id)object {
    if (self == object) {
        return YES;
    }

    if (![object isKindOfClass:[NSArray class]]) {
        return NO;
    }

    return [self isEqualToArray:(NSArray *)object];
}
@end

The following NSObject subclasses in Foundation have custom equality implementations, with the corresponding method:

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

The primary use case of object equality test for everyday object-oriented programming is to determine collection membership. To keep this fast, subclasses with custom equality implementations are expected to implement hash as well:

  • Object equality is commutative ([a isEqual:b][b isEqual:a])
  • If objects are equal, then their hash values must also be equal ([a isEqual:b][a hash] == [b hash])
  • However, the converse does not hold: two objects need not be equal in order for their has values to be equal ([a hash] == [b hash] ¬⇒ [a isEqual:b])

Comments