Best practices for overriding isEqual and hash

Total Post:17

 1488  View(s)
Rate this:

How to properly override isEqual: in Objective-C? The "catch" seems to be that if two objects are equal (as determined by the isEqual: method), they must have the same hash value.
The Introspection section of the Cocoa Fundamentals Guide does have an example on how to override isEqual:, copied as follows, for a class named ShowWidget:

- (BOOL)isEqual:(id)showOther {
    if (showOther == self)
        return YES;
    if (!showOther || ![showOther isKindOfClass:[self class]])
        return NO;
    return [self isEqualToWidget:showOther];
- (BOOL)isEqualToWidget:(ShowWidget *)showWidget {
    if (self == showWidget)
        return YES;
    if (![(id)[self name] isEqual:[showWidget name]])
        return NO;
    if (![[self data] isEqualToData:[showWidget data]])
        return NO;
    return YES;

It checks pointer equality, then class equality, and finally compares the objects using isEqualToWidget:, which only checks the name and data properties. What the example doesn't show is how to override hash.
Let's assume there are other properties that do not affect equality, say age. Shouldn't the hash method be overridden such that only name and data affect the hash? And if so, how would you do that? Just add the hashes of name and data? For example:

- (NSUInteger)myHash {
    NSUInteger myHash = 0;
    myHash += [[self name] myHash];
    myHash += [[self data] myHash];
    return myHash;

Is that sufficient? Is there a better technique? What if you have primitives, like int? Convert them to NSNumber to get their hash? Or structs like NSRect?

  1. Post:214

    Re: Best practices for overriding isEqual and hash

    I found this thread extremely helpful supplying everything I needed to get my isEqual: and hash methods implemented with one catch. When testing object instance variables in isEqual: the example code uses:
    if (![(id)[self name] isEqual:[showWidget name]])
        return NO;

    This repeatedly failed (i.e., returned NO) without and error, when I knew the objects were identical in my unit testing. The reason was, one of the NSString instance variables was nil so the above statement was:

    if (![nil isEqual: nil])
        return NO;

    and since nil will respond to any method, this is perfectly legal but

    [nil isEqual: nil]

    returns nil, which is NO, so when both the object and the one being tested had a nil object they would be considered not equal (i.e., isEqual: would return NO).
    This simple fix was to change the if statement to:

    if ([self name] != [showWidget name] && ![(id)[self name] isEqual:[showWidget name]])
        return NO;

    This way, if their addresses are the same it skips the method call no matter if they are both nil or both pointing to the same object but if either is not nil or they point to different objects then the comparator is appropriately called.
    I hope this saves someone a few minutes of head scratching.

      Modified On Sep-16-2015 11:09:00 PM