Confusion with distinct and indistinct object

134 views Asked by At

I thought that NSCountedSet counted numB and numC twice in the frequency because they had the same value, so I created two Fraction objects (not shown) from my class Fraction, and I set their ivars (numerator, denominator) to equal each others but the countForObject: treated them as two distinct objects and counted their frequencies as one each. numA and numB pointed to different places in memory but share the same value, and the two Fraction objects pointed to different places in memory but shared the same value. Why were the Number objects treated as indistinct, but not the Fraction objects?

#import <Foundation/Foundation.h>
#import "Fraction.h"

int main (int argc, char *argv[]) {
    @autoreleasepool {
        NSNumber *numA = [NSNumber numberWithInt: 1];
        NSNumber *numB = [NSNumber numberWithInt: 2];
        NSNumber *numC = [NSNumber numberWithInt: 2];

        NSArray *array = [NSArray arrayWithObjects: numA, numB, numC, nil];

        NSCountedSet *mySet = [[NSCountedSet alloc] initWithArray: array];
        for (NSNumber *myNum in mySet) {
            NSLog(@"Number: %i Frequency: %lu", [myNum intValue], [mySet countForObject: myNum]);
        }

        }
    return 0;
}

2012-08-05 17:44:58.667 prog[1150:707] Number: 1 Frequency: 1
2012-08-05 17:44:58.669 prog[1150:707] Number: 2 Frequency: 2
1

There are 1 answers

0
Jonathan Grynspan On BEST ANSWER

In order for your custom class to be recognized by NSCountedSet and by the rest of Foundation, you must implement -isEqual: and -hash correctly. By default, they compare pointers, so two objects will never compare equal even if they represent the same data. A naïve implementation of -isEqual: for a Fraction class would probably look like this:

- (BOOL)isEqual: (id)anObject {
    #error Naive implementation. Do not use.
    if (anObject == self) {
        return YES;
    } else if ([anObject isKindOfClass: [Fraction class]]) {
        Fraction *reducedSelf = [self reducedFraction];
        Fraction *reducedOther = [anObject reducedFraction];
        if (reducedSelf.numerator == reducedOther.numerator && reducedSelf.denominator == reducedOther.denominator) {
            return YES;
        }
    }

    return NO;
}

- (NSUInteger)hash {
    #error Naive implementation. Do not use.
    Fraction *reducedSelf = [self reducedFraction];
    return reducedSelf.numerator ^ reducedSelf.denominator;
}

Note that this would not allow you to compare instances of your Fraction class against instances of NSNumber. Because -isEqual: must be commutative and because equal objects must have the same hash value, you would need to provide an implementation that was compatible with NSNumber (probably by subclassing it and using NSNumber's implementations.)