24

How do I compare two objects of a custom class? My idea was to add an additional method to the class in which I can compare the current object with another object of the same kind.

So I can write my own code how each field of the class is compared.

This is how I would do it. Or are there some predefined methods to do that? Like "isEqualTo" of the NSString class?

Roatin Marth
  • 23,589
  • 3
  • 51
  • 55
TalkingCode
  • 13,407
  • 27
  • 102
  • 147

4 Answers4

57

The pointers to -isEqual: are good, but if you implement -isEqual:, you absolutely must also implement -hash in such a way that if two objects return YES for -isEqual: they will also return the same value for -hash. Implementing isEqual: without also implementing -hash leads to some very surprising bugs when you use Collections like NSArray.

For new developers, I tend to recommend against overloading -isEqual:. I recommend instead using the same technique as NSString, and create a custom -isEqualToFoo: (where Foo is your class) until you understand the impact of -isEqual: on collections and specifically want this behavior. Overloading -isEqual: powerful, but the bugs you can create are subtle. Creating your own custom comparator is safer and clearer in many cases.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Hi Rob. Great answer! Since I am a "new developer" I will do exactly as you suggested. This is what I had in mind in the first place. Many thanks. – TalkingCode May 18 '09 at 14:23
  • Thanks for pointing this out. While I still don't understand WHY isEqual isn't enough to determine equality, I was getting some REALLY strange NSCountedSet behavior because I had no idea that hash came into play with generic equality testing. – CIFilter Jun 25 '09 at 16:11
  • 9
    @LucasTizma You need to implement `hash` because its used for optimizations. `isEqual:` might be very expensive. Consider a massive `NSString`. You have to compare every character. Instead, you first check `hash`. This is a simple number, so the comparison is very fast, and for bucket-like data structures you've already calculated it anyway. If the hashes are equal, only then is `isEqual:` called. It's ok for two unequal things to have the same hash, and the simplest legal hash method is `return 1;`. But this can hurt performance if there are many equality checks and `isEqual:` is expensive. – Rob Napier Apr 20 '11 at 14:40
6

The standard way is to override - (BOOL)isEqual:(id)anObject and - (NSUInteger)hash.

You should read the documentation for NSObject protocol and this SO question has some interesting answers on how to write your hash method.

Community
  • 1
  • 1
pgb
  • 24,813
  • 12
  • 83
  • 113
1

Look at the isEqual: and the compare: method.

Georg Schölly
  • 124,188
  • 49
  • 220
  • 267
0

I have the following object:

#import <Foundation/Foundation.h>

typedef NS_ENUM(NSUInteger, SeasonType) {
    kWinter,
    kSpring,
    kSummer,
    kFall
};

@interface Season : NSObject

@property (nonatomic) SeasonType season;
@property (nonatomic) NSUInteger year;

+(id) seasonWithYear:(NSInteger)year season:(SeasonType)season;
-(id) initWithYear:(NSInteger)year season:(SeasonType)season;

@end

What I do is overwrite base NSObject comparison methods, there's no need of reinventing the wheel and code keeps cleaner as well:

#import "Season.h"

@interface Season()

@end

@implementation Season

+(id) seasonWithYear:(NSInteger)year season:(SeasonType)season{
    return [[self alloc] initWithYear:year season:season];
}

-(id) initWithYear:(NSInteger)year season:(SeasonType)season{
    self = [super init];
    if (self)
    {
        _year = year;
        _season=season;
        _baseDate=nil;
    }

    return self;
}

#pragma mark - NSObject

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

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

    return [self _isEqualToSeason:(Season *)object];
}

- (NSUInteger)hash {
    return self.season ^ self.year;
}


#pragma mark - Private/Internal

- (BOOL)_isEqualToSeason:(Season *)season {
    if (!season) {
        return NO;
    }

    return ((!self.season && !season.season) || self.season == season.season) &&
    ((!self.year && !season.year)    ||  self.year == season.year) ;
}

@end

Usage:

Season *season2 = [Season seasonWithYear:2010 season:kFall];
Season *season3 = [Season seasonWithYear:2009 season:kFall];
[season2 isEqual:season3];