简体   繁体   中英

*[NSMutableSet addObject: X]* adds object X even though *[Y isEqual:X]* returns TRUE for an object Y already in the Set

I have a Objective-C class called FactorHelper whose definition is below. It has a property called factors which is an NSMutableArray of NSNumbers . I have a custom isEqual: method in this class that returns true if the factors property in the two FactorHelper objects have the same numbers (even if the numbers are in a different order).

I tried to test by creating two FactorHelper objects one with 10,5,2 and the other with 10,2,5 . Then I created a NSMutableSet , added firstObject and then second object. I was expecting second object to be not added, but I see that it is added. When I step through the code I find that isEqual is being called by addObject and is returning TRUE . What am I doing wrong?

UPDATE

Changing the [NSMutableSet new] to [NSMutableSet alloc] init] makes things work as expected.

Also, changing all the TRUE, FALSE is isEqual to YES, NO makes it behave correctly (even if I keep it as [NSMutableSet new] ).

I have no idea what is going on. Can someone please shed some light?!

Class definition

@interface FactorHelper: NSObject
 @property NSMutableArray <NSNumber *> *factors;
 -(BOOL) isEqual:(FactorHelper *)other;
 -(instancetype) initWithFactors:(NSMutableArray *)factors;
 -(NSString *) description;
@end

@implementation FactorHelper

- (instancetype) initWithFactors:(NSMutableArray *)factors
{
    self = [super init];

    if (self) {
        _factors = factors;
    }

    return self;
}

-(BOOL) isEqual:(FactorHelper *)other
{
    if ([self.factors count] != [other.factors count])
    {
        return FALSE;

    }
    else
    {
        NSMutableDictionary <NSNumber *, NSNumber *> *myHashTable = [[NSMutableDictionary alloc] init];
        for (NSNumber *nextNumber in self.factors) {
            if(myHashTable[nextNumber] == nil)
            {
                myHashTable[nextNumber] = @(1);
            }
            else
            {
                myHashTable[nextNumber] = @([myHashTable[nextNumber] integerValue]+1);
            }
        }

        for (NSNumber *nextNumber in other.factors)
        {
            if(myHashTable[nextNumber] == nil)
            {
                return FALSE;
            }
            else
            {
                myHashTable[nextNumber] = @([myHashTable[nextNumber] integerValue] - 1);

                if ([myHashTable[nextNumber] integerValue] == 0) {
                    [myHashTable removeObjectForKey:nextNumber];
                }
            }
        }

        if ([[myHashTable allKeys] count] == 0)
        {
            return TRUE;
        }
        else
        {
            return FALSE;
        }

    }
}
@end

Unit test code

NSMutableSet *testSet = [NSMutableSet new];
FactorHelper *fact1 = [[FactorHelper alloc] initWithFactors:[@[@(10),@(5),@(2)] mutableCopy]];
FactorHelper *fact2 = [[FactorHelper alloc] initWithFactors:[@[@(10),@(2),@(5)] mutableCopy]];
[testSet addObject:fact1];
[testSet addObject:fact2];
NSLog(@"Are factors 1 and 2 the same: %d",[fact1 isEqual:fact2]);

NSMutableSet is a hash value based set. You need to override hash method for its element types consistent with isEqual: .

In your case, something like this:

- (NSUInteger)hash {
    NSCountedSet *factorCounts = [[NSCountedSet alloc] initWithArray:self.factors];
    return [@"FactorHelper" hash] + [factorCounts hash];
}

I'm not sure how you checked if I see that it is added , but this makes your FactorHelper work with NSMutableSet .

By the way, your isEqual: can be implemented a little bit shorter utilizing NSCountedSet .

-(BOOL) isEqual:(FactorHelper *)other {
    NSCountedSet *myFactorCounts = [[NSCountedSet alloc] initWithArray:self.factors];
    NSCountedSet *otherFactorCounts = [[NSCountedSet alloc] initWithArray:other.factors];
    return [myFactorCounts isEqual:otherFactorCounts];
}

This shows clearer consistency with the hash above.

Your code was never working, even if it sometimes appeared to.

The problem is that a custom implementation of isEqual is not the only requirement for making a class work in a Set. Think: what is a Set? It is a hash table . So you must also provide a matching custom implementation of hash — and you have not done that.

The requirement for hashability is: the hash value of two objects must be the same if those two objects are equal.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM