Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Converting NSArray to NSSet, custom class instances transfer inconsistently

Ran into a interesting little problem. I was writing a method to filter an array to the unique objects:

- (NSArray*)distinctObjectsByAddress {
    NSSet* uniqueSet = [NSSet setWithArray:self];
    NSArray* retArray = [uniqueSet allObjects];

    return retArray;
}

and wrote a unit test to check:

- (void)testDistinctObjectsByAddress5 {
    Person* adam1 = [[Person alloc] initWithFirstName:@"adam" lastName:@"adam" andParent:nil];
    Person* adam2 = [[Person alloc] initWithFirstName:@"adam" lastName:@"adam" andParent:nil];

    testPersonArray = [NSArray arrayWithObjects:adam1,adam2, nil];

    NSArray* checkArray = [testPersonArray distinctObjectsByAddress];

    STAssertEquals([checkArray count], [testPersonArray count], @"Array %@ counts should match %@ %@",checkArray,adam1,adam2);
}

Pretty simple. The interesting part is that about 80-90% of the time the test passes and every so often it fails because the distinctObjectsByAddress method only returns one object. I've been able to trace it to the [NSSet setWithArray:self] call but I've also been able to verify that the two person objects are two different objects (at least they have different address). I'm assuming that setWithArray: is just doing a basic address compare but I don't understand why it is sometimes producing two objects like it should and sometimes producing only one.

Something I just tried was changing adam2 so that the first and last name were not exactly the same as adam1. This seems to fix the error. Does this point to some sort of compiler optimization when the objects are logically the same?

like image 407
ACBurk Avatar asked Feb 20 '12 01:02

ACBurk


1 Answers

I'm assuming that setWithArray is just doing a basic address compare

That's incorrect. NSSet uses the -isEqual: and -hash methods on the objects that are added to it. It depends on how those are implemented in Person or its superclasses.

If [person1 isEqual:person2] then you would expect the set to contain one object. If not, then the set should contain two objects.

My guess is that Person does not follow the rules in its -isEqual: and -hash methods. Most likely, the two objects are equal, but their hashes are not equal like they should be. (Except for the 10-20% of the time that you're getting lucky.)

Does this point to some sort of compiler optimization when the objects are logically the same?

No, there is no compiler optimization that would merge the two objects into one.

like image 121
Kurt Revis Avatar answered Sep 30 '22 18:09

Kurt Revis