Duplicated custom object in NSSet
Asked Answered
F

1

5

I have some problems about the NSMutableSet in Objective-C. I learnt that the NSSet will compare the two objects' hash code to decide whether they are identical or not. The problems is, I implemented a class that is subclass of NSObject myself. There is a property NSString *name in that class. What I want to do is when instances of this custom class has the same variable value of "name" , they should be identical, and such identical class should not be duplicated when adding to an NSMutableSet.

So I override the - (NSUInteger)hash function, and the debug shows it returns the same hash for my two instances obj1, obj2 (obj1.name == obj2.name). But when I added obj1, obj2 to an NSMutableSet, the NSMutableSet still contained both obj1, obj2 in it.

I tried two NSString which has the same value, then added them to NSMutableSet, the set will only be one NSString there.

What could be the solution? Thank you for any help!

The custom Class: Object.h:

#import <Foundation/Foundation.h>

@interface Object : NSObject

@property (retain) NSString *name;

@end

Object.m

@implementation Object
@synthesize name;

-(BOOL)isEqualTo:(id)obj {
    return [self.name isEqualToString:[(Object *)obj name]] ? true : false;
}

- (NSUInteger)hash {
    return [[self name] hash];

}
@end

and main:

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

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        Object *obj1 = [[Object alloc]init];
        Object *obj2 = [[Object alloc]init];
        obj1.name = @"test";
        obj2.name = @"test";
        NSMutableSet *set = [[NSMutableSet alloc] initWithObjects:obj1, obj2, nil];
        NSLog(@"%d", [obj1 isEqualTo:obj2]);
        NSLog(@"%ld", [set count]);
    }
    return 0;
}
Fluff answered 25/10, 2013 at 2:15 Comment(3)
Did you also override isEqual:?Liaotung
Can you show your implementation of hash, isEqual and how you add the objects to the set?Liaotung
You implemented the wrong method. isEqual: is not the same as isEqualTo: You also can't assume that the object you're testing for equality even has a property called name. You should have a read of Mike Ash's post about equality and hashing.Hager
L
8

Instead of implementing isEqualTo: you have to implement isEqual:

- (BOOL)isEqual:(id)object {
    return [object isKindOfClass:[MyObject class]] &&
           [self.name isEqual:[(MyObject *)object name]];
}

This will (probably falsely) return NO if both self.name and object.name are nil. If you want to return YES if both properties are nil you should use

- (BOOL)isEqual:(id)object {
    if ([object isKindOfClass:[MyObject class]]) {
        return (!self.name && ![(MyObject *)object name]) ||
        [self.name isEqual:[(MyObject *)object name]];
    }
    return NO;
}
Liaotung answered 25/10, 2013 at 2:38 Comment(3)
Using [self class] can lead to [a isEqual:b] != [b isEqual:a], which I think should never be desirable (when b is a subclass of a without overriding isEqual:).Liaotung
More specifically, if a is an instance of the subclass of b's class, [a isEqual:b] will return NO (because b is not kind of a's class) and [b isEqual:a] will return YES, because a is kind of b's class. (when using [self class])Liaotung
No worries. I also updated my answer in regards to both name properties being nil.Liaotung

© 2022 - 2024 — McMap. All rights reserved.